Frontend Conference 2019 #kfug2019 の登壇資料です
ϨΨγʔͳϑϩϯτΤϯυΛϦϓϨΠε͢ΔFrontend Conference 2019 #kfug2019@Daikids2
View Slide
খౡ େج / Daiki Kojima@Daikids2@jiko21ژେֶେֶӃใֶݚڀՊM2(ଟ) ϑϩϯτΤϯυΤϯδχΞ
͓ͳ͢͠Δ͜ͱ• ϨΨγʔͳϑϩϯτΤϯυΛϦϓϨΠεͨ͠• ϨΨγʔͬͯͲΜͳঢ়ଶͩͬͨͷ?• ٕज़બఆ? ઃܭ?• Ͳ͏ͬͯ͢͢Ί͍ͯͬͨͷ?
ͦͦ
ϨΨγʔͬͯ?
Wikipediaͩͱ…https://ja.wikipedia.org/wiki/%E3%83%AC%E3%82%AC%E3%82%B7%E3%83%BC%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0
ཁ͢Δʹ…• ݹ͍!!• ෛ࠴• ѻ͍ͮΒ͍…
ϑϩϯτͬͯ ΘΓ͔͠৽͍͠…
ϨΨγʔͱؔͳ͍?
NO!
ϑϩϯτϨΨγʔʹͳΔΘ͚• ৽͍ٕ͠ज़͕͙͢ʹೖͬͯ͘Δ• TypeScript, es6…• ഁյతͳมߋɾUpdate͕ଟ͍• Angular 1.x/2.xͰͷҧ͍
࣌ͷٕज़ελοΫ• Angular.js (1ܥ)• Vue.js (2ܥ) + TypeScript• Pug + Sass• Gulp
࣌ͷٕज़ελοΫ• Angular.js (1ܥ)• ։ൃॳظʹಋೖ (Angular (2ܥ)͕ొͨ͜͠Ζ)• JavaScriptͰهड़
࣌ͷٕज़ελοΫ• Vue.js (2ܥ)• ʮAngular.js͕ͭΒ͍ΑͶʯͱ͍͏͜ͱͰ৽نը໘Ͱ ಋೖ• TypeScript + Pug + SassͰهड़
Ϗϧυ·ΘΓ?
࣌ͷٕज़ελοΫVue.jsAngular.jsGulp html, css, js"OHVMBSͱ7VFKTΛ (VMQͰϏϧυ
ϨΨγʔɾ·͍ͣ
ͬ͟ͱ(Ұྫ)• Angular.jsͱVue.js͕ڞଘ• Angular.jsଆςετ͕Ұͳ͠!!• i18nରԠ͕ͻͲ͍ (ຊޠɾӳޠΛผʑͷϑΝΠϧͰදݱ)
ͬ͟ͱ• Angular.jsͱVue.js͕ڞଘ• Ұ෦Service͕Angularʹґଘ• VueͷComponentͰXHRͯ͠Δ• Angular.jsଆςετ͕Ұͳ͠!!• i18nରԠ͕ͻͲ͍ (ຊޠɾӳޠΛผʑͷϑΝΠϧͰදݱ)͕ଟ͍
Angular.jsͱVue.js͕ڞଘ
Angular.jsͱVue.js͕ڞଘ• 2ͭͷFWΛ๊͑Δ͜ͱʹ…• Angular.jses5ɺVue.jsTypeScript(es6 base)• FWͲ͜Ζ͔ɺݴޠ(͍ͩͿ)ҧ͏• Ұ෦Service͕Angularʹґଘ• ࠶ར༻ੑ͕͍➡ VueͷComponentͰXHRͤ͟ΔΛಘͳ͍߹
Angular.jsଆςετ͕Ұͳ͠!!
ςετͬͯ?ιʔείʔυࣦഊޭίʔυ͕ςετέʔεΛύε͢Δ͔ ࣮ߦͯ֬͠ೝςετπʔϧςετ࣮ߦ
ςετΛॻ͘ར• ڍಈ͕ਖ਼͍͜͠ͱΛอূͰ͖Δ• ͷΓ͚͕Ͱ͖Δ• ςετέʔε͕༷ʹͳͬͯ͘ΕΔ• etc…
ͱ͜Ζ͕…• ςετͷݱঢ়• Vue: ίϯϙʔωϯτͷςετ͋Δ• Angular.js: ςετ͕Ұͳ͠(Ϟδϡʔϧ!!)
i18nରԠ͕ͻͲ͍
i18nͬͯ• ࠃࡍԽରԠͷ͜ͱ• ༷ʑͳҬɾݴޠʹ߹ΘͤΔ͜ͱຊޠͰ ݟ͍ͨӳޠͰݟ͍ͨຊޠ൛ӳޠ൛
ຊདྷͳΒ…• ઃఆϑΝΠϧͰݴޠΛཧ͓ͯ͘͠• ϒϥβͷใΛ༻͍ͯද͖ࣔ͢ݴޠͰදࣔ• i18nαϙʔταʔόʔɾϑϩϯτΘͣ ༷ʑͳϥΠϒϥϦͰ࣮ݱͰ͖Δ
ݱঢ়• ଟݴޠରԠ͕ඞཁͳϖʔδӳޠ൛ɾຊޠ൛ͱ ςϯϓϨʔτ(html)Λ͚ͯɺϦϯΫผʑʹ…➡ ίʔυͷมߋྔ͕ݴޠͷ͚ͩ૿͑Δ!hogehoge-ja.html(ຊޠ) hogehoge-en.html(ӳޠ)
ϨΨγʔΛվળ͢ΔͨΊʹϦϓϨΠεΛߦ͏͜ͱʹ!!
ٕज़બఆɾઃܭ
Δ͜ͱ• ٕज़ΛબͿ• FWɺUI Architectureɺςετ• σΟϨΫτϦͷઃܭ
ٕज़બఆ• FW: Vue.js• ίϨࣗମܾఆࣄ߲• @vue/cliͰϘΠϥʔϓϨʔτΛੜ
ٕज़બఆ• ͦͷଞ• TypeScript: ࣾͰͲΜͲΜTSԽ͕… ܕ͋Δͱ͏Ε͍͠• class-style-api: ܕָ͕ɻͰ…(ޙड़)• Vuex: ෳࡶͳঢ়ଶཧ͕ඞཁͳ߹
Class-style apiClass-style api@Componentexport default class Count extends Vue {@Prop() msg!: string;private count: number = 0;private add(): void {this.count += 1;}private minus(): void {this.count -= 1;}get isEven(): boolean {return this.count % 2 === 0;}}export default Vue.extend({name: "Count",props: {msg: {type: String,required: true,},},data() {return {count: 0,};},computed: {isEven(): boolean {return this.count % 2 === 0;},},methods: {add(): void {this.count += 1;},minus(): void {this.count -= 1;},},});Class-style apiҰൠతͳॻ͖ํ
σΟϨΫτϦઃܭɹ• QiitaͳͲΛௐͯσΟϨΫτϦߏΛத৺ʹใूΊ• React + ReduxΛܦݧ͔ͯ͠ΒͷstatelessͳVue + Vuexߏʹ͍ͭͯ (https://qiita.com/_masakitm_/items/ff5df4da0247baeede35)• vuexެࣜ (https://github.com/vuejs/vuex/tree/dev/examples/chat)
Ͱ͖͕͋ͬͨͷ6*·ΘΓϩδοΫ·ΘΓ
UI·ΘΓ1BHFT 1BHFT 1BHFT/home /about /helpDPOUBJOFS DPOUBJOFSDPNQPOFOU DPNQPOFOUDPNQPOFOUʔ͡͝ͱʹ 1BHFͷ3PPU$PNQPOFOU7VFY4UPSFΛ࣋ͯΔ DPOUBJOFSDPNQPOFOUجຊతʹTUBUFMFTTͳ $PNQPOFOU7VFY
ϩδοΫपΓ• جຊతʹserviceɺutilͳͲͷܗͰϞδϡʔϧΛ۠Δ• service: APIͳͲΛୟ͘• utils: ࣌ؒɺจࣈྻͳͲͷศརͳؔ• ͜ΕΒΛVue͔ΒҾ͖ണ͕͠ɺ UIϑϨʔϜϫʔΫʹґଘ͠ͳ͍ɺ࠶ར༻Մೳͳͷʹ
VuexपΓ• جຊతʹϖʔδ͝ͱʹmoduled store• άϩʔόϧͰڞ༗͢Δඞཁ͋·Γͳ͍• ͨͩ͠moduleͳ͍Ͱঢ়ଶ͕૿͑Δ߹…• ؔ৺͝ͱϕʔεͰmoduleΛ۠Δ (re-ducksύλʔϯ?)
ҙࣝͨ͜͠ͱ• ࣾͰReactΛಋೖͨ͠ͱ͖ͷলΛ׆͔͢• ComponentͳΔ͘Statelessʹ• XHR͠ͳ͍• ԿΑΓɺίʔυΛࣺͯΒΕΔΑ͏ʹ͢Δ• Vueʹґଘ͠ͳ͍ॲཧmoduleʹ
ςετपΓ• ީิʹ্͕ͬͨπʔϧ• Jest• Mocha+ChaiͲͪΒ!WVFDMJͰબՄೳ
ͦΕͧΕͷҧ͍…• Jest vs Mocha: Which Should You Choose?[1]ʹΑΔͱ…• Jest: ؆୯ʹ͔͚ͯฒߦ࣮ߦɻεφοϓγϣοτɹ• Mocha: ΧελϜ͕Ͱ͖Δ…[1] https://blog.usejournal.com/jest-vs-mocha-whats-the-difference-235df75ffdf3ॻ͖͢͞͞Ͱ+FTU
ςετํ• ϞδϡʔϧɾVuex• ΧόϨοδ100%Λࢦ͢• ίϯϙʔωϯτ• εφοϓγϣοτςετΛଟ༻• ΠϕϯτͰ͖ΔݶΓνΣοΫ
Snapshotςετͱ?ඳը{{ count }}+-ίϯϙʔωϯτexports[`Count.vue correctly renders html 1`] = `0+-εφοϓγϣοτϑΝΠϧʹ ॻ͖ࠐΉॳճ
Snapshotςετͱ?ඳը{{ count }}+-ίϯϙʔωϯτEJ⒎Λͱͬͯ ֬ೝͦΕҎ߱is equal toඳը͞ΕͨComponent?SnapshotϑΝΠϧ
εφοϓγϣοτςετͷॴɾॴϝϦοτ σϝϦοτɾมߋ͕Θ͔Γ͍͢ɾมߋʹऑ͍ɾ6*ϥΠϒϥϦʹґଘɾ࣮ߦڥʹґଘ
ଞʹ…• ίʔυͷελΠϧ໘ͰͷࢦఠΛRVͰݮΒ͢• ESLint• SnapshotςετͷมߋΛ༻ҙʹ• watch(ࢹ)ϞʔυͷςετΛ༰қ
࣮ࡍͷ։ൃʹ͍ͭͯ
։ൃͷྲྀΕ• ֤ϖʔδ͝ͱͷissue• ୲ऀΛAssigneeʹ• ը໘ɾϩδοΫ·ΘΓ(+Vuex)͝ͱʹPR• جຊϦϓϨΠεݩͷը໘ΛͳΔ͘࠶ݱ
ϓϩδΣΫτͷਐΊํҰਓͰ ։ൃΛਐΊΔ࣍ୈʹଞͷਓ ։ൃʹࢀՃνʔϜશମͰ ϦϓϨΠεΛ ऴ͍ྃͤͯ͘͞5݄ 9݄7݄ϦϓϨΠεྃϦϓϨΠε4UBSUҰ൪Ή͍ͣͭAngular.jsϦϓϨΠε։࢝طଘͷVue͔Β
։ൃॱௐ͔ͩͬͨ?
NO
ͳͥ͏·͍͔͘ͳ͍͔• ϑϨʔϜϫʔΫϥΠϒϥϦৗʹมԽ͍ͯ͠Δ• ಛʹRFCʹ্͕ΔΑ͏ͳٕज़࠾༻͞Εͳ͜ͱ• npm͕rc൛ͷϥΠϒϥϦΛinstall͢Δ͜ͱ͋Δ
࣮ࡍʹ͋ͬͨ͜ͱ• ϦϓϨΠεલͷίʔυͰಈ͍͍ͯͨςετ͕ ಈ͔ͳ͍• @vue/cli͕͏Α͏ʹΞϐʔϧͯͨ͠class-style-api͕ ඇਪʹ
ϦϓϨΠεલͷίʔυͰ ಈ͍͍ͯͨςετ͕ಈ͔ͳ͍
ϦϓϨΠεલͷίʔυͰ ಈ͍͍ͯͨςετ͕ಈ͔ͳ͍• ݪҼ => Bootstrap-Vueͷόʔδϣϯ• Ҡߦલ: 1.5ܥ• Ҡߦઌ: 2.0.x-rc• ͦͦComponentͷmount͕͏·͍ͬͯ͘ͳ͍…
ରॲ๏• ςετΛॻ͘͜ͱ͕తͰͳ͍ͷͰҰ୴Skip• ۩ମతʹ… • ςετίʔυΛ͠ͳ͕Β࣮ߦΛඈͤΔit.skip('correctly renders html', () => {const wrapper = shallowMount(Count);expect(wrapper.html()).toMatchSnapshot();});
@vue/cli͕͏Α͏ʹ Ξϐʔϧͯͨ͠class-style-api͕ ඇਪʹ
@vue/cli͕͏Α͏ʹΞϐʔϧͯͨ͠ class-style-api͕ඇਪʹ• ઃܭஈ֊ͰTypeScriptΛ͏લఏͷͨΊར༻• Vue.extendsΑΓܕ·ΘΓ͕ྑ͔ͬͨ…• class style apiͷར༻Λܾఆͨ࣌͠ظ• 20191݄• ͜ΕΛͬͯ։ൃΛਐΊ͍ͯͨ…
͋Δ…https://github.com/vuejs/rfcs/pull/17#issuecomment-494242121
͜ΕΛ౿·͑ͯɹ• ৽نͰ։ൃ͢ΔComponentʹؔͯ͠• class style apiΛར༻ͤͣʹ࣮• ͢Ͱʹ։ൃͨ͠Componentʹ͍ͭͯ• Ұ୴อཹ (͙͢͞·αϙʔτ͞Εͳ͘ͳΒͳ͍ͨΊ)• ͨͩ͠issueͱͯ͠ഉআܭըΛ͓ͯ͘͠ (ϦϑΝΫλϦϯά)
࠷ޙʹ
ϦϓϨΠεΛऴ͑ͯ• VueࣗମͰͷϦϓϨΠεϦϦʔεྃ• ྑ͔ͬͨ͜ͱ• ࠓ·Ͱٕ͋ͬͨज़తෛ࠴ҰͰ͖ͨɻ• ϞμϯͳϑϩϯτΤϯυͷݟ• ѱ͔ͬͨ͜ͱ• ٕज़બͷϛεʹΑΔ৽ͨͳ՝• ଞʹࠓ͔ΒݟΔͱGoodͰͳ͍ίʔυ
ϦϓϨΠεΛऴ͑ͯ• VueࣗମͰͷϦϓϨΠεϦϦʔεྃ• ྑ͔ͬͨ͜ͱ• ࠓ·Ͱٕ͋ͬͨज़తෛ࠴ҰͰ͖ͨɻ• ϞμϯͳϑϩϯτΤϯυͷݟ• ѱ͔ͬͨ͜ͱ• ٕज़બͷϛεʹΑΔ৽ͨͳ՝• ଞʹࠓ͔ΒݟΔͱGoodͰͳ͍ίʔυࠓޙϦϑΝΫλϦϯάͰ ղফ
·ͱΊ• ϑϩϯτΤϯυ࣌ͱͱʹϨΨγʔͳίʔυ͕ݱΕΔ• Λղܾ͠ɺ։ൃͷ͢͠͞Λ֬อ͢ΔͨΊʹ ϦϓϨΠεେࣄɻ• ϦϓϨΠεޙͷΞϓϦʹඞͣ͋Γɺ ϦϑΝΫλϦϯά͍ͯ͘͜͠ͱ͕ॏཁɻ
͝ਗ਼ௌ ͋Γ͕ͱ͏͍͟͝·͢