Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vue.js 2.0 Server Side Rendering

kazupon
November 13, 2016

Vue.js 2.0 Server Side Rendering

Nodefest 2016 Nov 13

kazupon

November 13, 2016
Tweet

More Decks by kazupon

Other Decks in Programming

Transcript

  1. ࡞ऀ Evan You ※ ࡞ऀͷΠϯλϏϡʔهࣄ: Between the Wires | Evan

    You
 https://betweenthewires.org/between-the-wires-evan-you-cb56660bc8a4#.6b0vzpgzw ※ ࡞ऀͷαΠτ
 http://evanyou.me
  2. • 2016೥10݄1೔(೔ຊ࣌ؒ)
 ίʔυωʔϜ: Ghost in the Shell
 ϦϦʔεʂ ࠷৽όʔδϣϯ 2.0

    2017 2014 2015 2016 spawn 0.6 0.8 0.9 0.10 0.11 0.12 1.0 2.0 2013 ※ Πϥετஶ࡞ऀ: @hashedrock ࢯ https://twitter.com/hashedrock/status/782069763358924800 Animatrix Blade
 Runner Cowboy
 Bebop Dragon
 Ball Evangelion ghost in the shell
  3. ϨϯμϦϯάͷߴ଎Խ ※ The Vue Point: Vue 2.0 is Here (Vue.js

    ެࣜϒϩά)
 https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.v9xl2x8f7
  4. Vue ͷΑ͋͘ΔҰൠతͳίʔυ <html> <head> ... <script src="./vue.js"></script> </head> <body>
 ...

    <div id="app"></div>
 <script> new Vue({ template: `<div> <p>message: {{msg}}</p> </div>`, data: { msg: 'hi evan!!' } }).$mount('#app') </script>
 ... </body> </html>
  5. ςϯϓϨʔτΛ AST ʹม׵ • ςϯϓϨʔτύʔαΛར༻ͯ͠AST (Abstract Syntax Tree) ʹม׵ <div

    id="app"> <h1>title</h1> <p class="msg"> msg: {{msg}} </p> </div> Template { tag: 'div', type: 1, children: [{ tag: 'h1', type: 1, children: [{ type: 3, text: 'title', … }], … }, { tag: 'p', type: 1, children: [{ type: 2, text: 'msg: {{msg}}', … }], … }] } compile AST
  6. ςϯϓϨʔτύʔα • ςϯϓϨʔτύʔα͸ɺJohn Resig ࢯ੡ͷ΋ͷΛ fork ͠ ͯ Vue ޲͚ʹಠࣗʹΧελϚΠζ

    ※ ςϯϓϨʔτύʔα
 https://github.com/vuejs/vue/blob/dev/src/compiler/parser/html-parser.js ES2015 Ͱ re-write & ࠷దԽ
  7. Ұൠతͳ Virtual DOM New Virtual Node Tree Old Virtual Node

    Tree diff + patch Ծ૝ϊʔυπϦʔͷ diff ࢉग़ίετ͕͔͔Δ
  8. 1. ੩తͳϊʔυͷݕग़ • ҎԼͷΑ͏ͳςϯϓϨʔτͷέʔεͷ৔߹͸ <div id="app"> <div class="header">title: {{title}}</div> <ul

    style="list-style-type: none" class="menu"> <li style="margin: 0" :class="{ even: isEven(index), odd: isOdd(index) }" v-for="(item, index) in items"> menu{{index}}: {{item}} </li> </ul> <div class="content"> <p>This is content</p> </div> </div> divɺpɺͦͯ͠ text ཁૉΛ ੩తͳϊʔυͱͯ͠ݕग़
  9. • ݕग़͞Εͨϊʔυ͸ɺAST ʹ static = true ͱͯ͠ϚʔΩ ϯά͞ΕΔ 1. ੩తͳϊʔυͷݕग़

    { tag: 'div', children: [{ tag: 'div', … }, { tag: 'ul', children: […], … }, { tag: 'div', static: true, children: [{ tag: 'p', static: true, children: [{ static: true, text: 'This is content', … }] }], … }] }
  10. 2. ੩తͳϊʔυπϦʔͷݕग़ • ੩తͳϊʔυΛࢠͱͯ࣋ͭ͠ϧʔτ(root)ͱͳΔཁૉ͕ ͋Δ͔Ͳ͏͔νΣοΫ͢Δ <div id="app"> <div class="header">title: {{title}}</div>

    <ul style="list-style-type: none" class="menu"> <li style="margin: 0" :class="{ even: isEven(index), odd: isOdd(index) }" v-for="(item, index) in items"> menu{{index}}: {{item}} </li> </ul> <div class="content"> <p>This is content</p> </div> </div> ͜ͷέʔεͰ͸ɺ͜ͷ div ཁૉ͕ ੩తͳϊʔυπϦʔͱͯ͠ݕग़
  11. 2. ੩తͳϊʔυπϦʔͷݕग़ • ݕग़͢ΔͱɺAST ʹ staticRoot = true ͱͯ͠ϚʔΩϯά͢Δ {

    tag: 'div', children: [ { tag: 'div', … }, { tag: 'ul', children: […], … }, { tag: 'div', static: true, staticRoot: true, children: [ { tag: 'p', static: true, children: [{ text: 'This is content', static: true, … }], … } ], … } ] }
  12. AST ͔ΒϨϯμϦϯάؔ਺Λੜ੒ • ҎԼͷؔ਺Λੜ੒͢Δ • render ؔ਺ • staticRenderFns ؔ਺܊

    (഑ྻ) • ͜ΕΒؔ਺͸ɺVue Πϯελϯ εͷ $options ʹ֨ೲ͞ΕΔ Optimized AST
  13. render ؔ਺ • Ծ૝ϊʔυπϦʔΛฦؔ͢਺
 (e.g. ࠷దԽͰઆ໌ͨ͠ASTΛrenderؔ਺Խͨ͠΋ͷ) function anonymous () {

    with (this) { return _h('div', { attrs: { 'id': 'app' } }, [ _h('div', { staticClass: 'header'}, ['title: 'ɹ+ɹ_s(title)]), _h('ul', { staticClass: 'menu', attrs: { style: 'list-style-type: none' } }, [_l((items), function (item, index) { return _h('li', { ɹɹɹɹɹɹɹɹɹɹkey: item.id, class: { even: isEven(index), odd: isOdd(index) }, attrs: { style:'margin: 0' } }, ['menu' + _s(index) + ': ' + _s(item.name)]) })] ), _m(0) ]) } ࠷దԽʹΑΓݕग़͞Εͨ੩తͳϊʔυπϦʔͷ෦෼͸ɺ ಺෦ϝιουʹϚοϐϯά
  14. staticRenderFns ؔ਺܊ • ࠷దԽʹΑΓ AST Ͱݕग़ͨ͠੩తͳϊʔυπϦʔ͸ɺ Ծ૝ϊʔυπϦʔͱͯ͠ฦ͢Α͏ʹੜ੒ͨؔ͠਺Λ഑ྻ ʹ֨ೲ͢Δ [ function

    anonymous () { with (this) { return _h('div', { staticClass: 'content' }, [ _h('p', ['This is content']) ]) }} ]
  15. Watcher ʹΑΔσʔλมߋͷ؂ࢹ • σʔλґଘؔ܎Λ௥੻͠ɺσʔλมߋΛ؂ࢹ͢Δ • σʔλมߋݕग़ͷ౎౓ɺ Watcher ͸಺෦ͷ _render ϝ

    ιουΛݺͼग़ͯ͠࠶ϨϯμϦϯά const Component = { props: ['msg'], data () { return { title: 'hello', items: […] } }, … } msg data/props Watcher title items 0 x … Collect Dependencies
  16. Ծ૝ϊʔυπϦʔͷੜ੒ • render ؔ਺ͱ staticRenderFns ؔ਺܊ͰԾ૝ϊʔυπϦʔ Λੜ੒ function anonymous ()

    { with (this) { return _h('div', { attrs: { 'id': 'app' } }, [ _h('div', { staticClass: 'header'}, ['title: 'ɹ+ɹ_s(title)]), _h('ul', { staticClass: 'menu', attrs: { style: 'list-style-type: none' } }, [_l((items), function (item, index) { return _h('li', { ɹɹɹɹɹɹɹɹɹɹkey: item.id, class: { even: isEven(index), odd: isOdd(index) }, attrs: { style: 'margin: 0' } }, ['menu” + _s(index) + ": “ + _s(item.name)]) })] ), _m(0) ]) } [ function anonymous () { with (this) { return _h('div', { staticClass: 'content' }, [ _h('p', ['This is content']) ]) }} ] staticRenderFns render
  17. Vue ͷ Virtual DOM • Virtual DOM ͸ snabbdom Λ

    fork ͯ͠ಠࣗʹΧελϚΠ ζͨ͠΋ͷ
 https://github.com/snabbdom/snabbdom
  18. Virtual DOM ͷ diff / patch • diff / patch

    ͸جຊ snabbdom ϕʔεͰɺVue ͷ࠷దԽॲ ཧʹΑͬͯ diff ΛεΩοϓ͢Δ͜ͱͰɺDOM Λੜ੒͢Δ ·ͰॲཧίετΛ࡟ݮ https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js
  19. Demystifying Frontend Framework Performance • Nodric.js 2016 Ͱൃදͨ͠ Evan You

    ࢯͷ্هλΠτϧͷࢿ ྉ͕Α͘·ͱ·͍ͬͯΔͷͰɺҰݟ͢Δ͜ͱΛ͓קΊ͢ Δ • εϥΠυ
 https://docs.google.com/presentation/d/ 1Ju5NryLLI-2aXm_XwsdF5rU0QpOpeyVW9F8JeeSuj-k/ edit#slide=id.p • ϏσΦ
 https://www.youtube.com/watch?v=Ag-1wmHWwS4
  20. ࠷దԽ߲໨ • ςϯϓϨʔτͷࣄલίϯύΠϧ + ϥϯλΠϜ • v-for ʹΑΔϦετϨϯμϦϯά࣌ʹ͓͚Δ key ଐੑ

    • render ؔ਺ʹΑΔύϑΥʔϚϯενϡʔχϯά࣮૷ • ؔ਺ܕίϯϙʔωϯτ (functional component)
  21. Vue 2.0 Ͱ͸؀ڥ͕େ͖͘վળ • Virtual DOM ʹΑΓந৅Խ͞ΕͨϨϯμϦϯά • ϋΠυϨʔγϣϯͷ࢓૊Έ •

    ϢχόʔαϧରԠ͞Εͨެࣜʹఏڙ͢ΔϥΠϒϥϦ • Node.js ޲͚ʹϨϯμϥ • ίϯςΩετʹΑΔႈ౳ੑΛอূ͢ΔϨϯμϦϯά • αʔό޲͚ʹ࠷దԽ͞ΕͨϞδϡʔϧͷόϯυϦϯά
  22. ఏڙ͢ΔϨϯμϥ • Vue ΞϓϦέʔγϣϯΛαʔόαΠυͰϨϯμϦϯά͢ ΔͨΊͷ Node.js ؀ڥͰಈ࡞͢ΔϨϯμϦϯάϞδϡʔ ϧ • Ϩϯμϥ͸ɺࢦఆ͞Εͨ

    Vue ΞϓϦέʔγϣϯͷrender ؔ਺ʹΑͬͯऔಘͨ͠Ծ૝ϊʔυπϦʔΛ walk ͯ͠ HTML จࣈྻͱͯ͠ϨϯμϦϯά
  23. جຊతͳ࢖͍ํ • NPMͰΠϯετʔϧ
 $ npm install vue-server-renderer • ϨϯμϥΛ࡞੒ͯ͠ɺrenderToString ͰϨϯμϦϯά

    const Vue = require('vue') const renderer = require('vue-server-renderer').createRenderer() const vm = new Vue({ render (h) { return h('div', 'hello') } }) renderer.renderToString(vm, (err, html) => { console.log(html) // -> <div server-rendered="true">hello</div> })
  24. Stream ʹΑΔϨϯμϦϯά • Node.js ͷ Stream API ΋αϙʔτɻrenderToStream Ͱ stream

    Λ࡞੒ͯ͠ɺstream ΠϯλʔϑΣΠεʹΑͬͯϊ ϯϒϩοΩϯάͳϨϯμϦϯά͕Մೳ app.get('/', (req, res) => { const vm = new App({ url: req.url }) const stream = renderer.renderToStream(vm) res.write(`<!DOCTYPE html><html><head><title>...</title></head><body>`) stream.on('data', chunk => { res.write(chunk) }) stream.on('end', () => { res.end('</body></html>') }) })
  25. Ωϟγϡʹ͓͚ΔඞࢸΠϯλʔϑΣΠε • Φϓγϣϯʹ ࢦఆ͢Δ cache ΦϒδΣΫτ͸ɺҎԼͷ ΠϯλʔϑΣΠεΛ࣮૷͍ͯ͠Ε͹ΩϟογϡՄೳ { get: (key:

    string, [cb: Function]) => string | void, set: (key: string, val: string) => void, has?: (key: string, [cb: Function]) => boolean | void // optional }
  26. ྫ: RedisClient ʹΑΔΩϟογϡ const redisClient = require('redis').createClient() const renderer =

    createRenderer({ cache: { get: (key, cb) => { redisClient.get(key, (err, res) => { // handle error if any cb(res) }) }, set: (key, val) => { redisClient.set(key, val) } } })
  27. ྫ: ίϯϙʔωϯτͷΩϟογϡରԠ export default { name: 'item', // required props:

    ['item'], serverCacheKey: props => props.item.id, render (h) { return h('div', this.item.id) } }
  28. • αʔόαΠυͰϨϯμϦϯά͞ΕͨDOMπϦʔͱɺΫϥΠΞϯτα ΠυͰߏங͞ΕΔԾ૝ϊʔυπϦʔ͕Ұக͢Δ͔Ͳ͏͔ͷݕূͱ ࠶ߏங·ͰͷҰ࿈ͷࣄ৅ͷ͜ͱ ۩ମతʹϋΠυϨʔγϣϯͬͯʁ HTTP App (Root vue instance)

    Render Functions ... <div id="app" server-rendered="true"> ... </div> ... response hydrate JSON new Vue({ … }).$mount(‘#app’) render Virtual Node Tree Rendered DOM Tree window.__initial__ = { …. } checking set $el
  29. • αʔόαΠυͰϨϯμϦϯά͞ΕͨDOMπϦʔΛഁغͯ͠ɺΫϥΠ ΞϯταΠυͰैདྷͷVirtual DOM ͷॲཧϑϩʔͰϨϯμϦϯά αʔόͱΫϥΠΞϯτ͕Ұக͠ͳ͔ͬͨ৔߹ HTTP App (Root vue

    instance) Render Functions ... <div id="app" server-rendered="true"> ... </div> ... response hydrate JSON new Vue({ … }).$mount(‘#app’) Rendered DOM Tree window.__initial__ = { …. } checking render Virtual Node Tree diff + patch
  30. ϋΠυϨʔγϣϯͷ஫ҙࣄ߲ • αʔόαΠυϨϯμϦϯάͱΫϥΠΞϯτͷϋΠυϨʔ γϣϯʹ͓͍ͯɺValid ͳߏ଄Λͨ͠ HTML Λهࡌ͠ͳ ͍ͱෆҰக͕ൃੜ͢Δ <table> <tr><td>hi

    evan!</td></tr> </table> ϒϥ΢β͸ࣗಈతʹ <tbody>Λૠೖ͢Δ Vue͸ςϯϓϨʔτΛίϯύΠϧ͢Δͱ ͦͷߏ଄ͷ·· Virtual-DOM Λੜ੒͢Δ ෆਖ਼ͳ<table>ߏ଄Λ࣋ͬͨ
 ςϯϓϨʔτ
  31. ίϯςΩετͷಋೖ • αʔό޲͚ͷ Vue ΞϓϦέʔγϣϯΛ࣮ߦͤ͞ΔΤϯτ ϦϙΠϯτΛ༻ҙ͢Δ // server-entry.js import Vue

    from 'vue' import App from './App.vue' const app = new Vue(App) // όϯυϧϨϯμϥଆͰϨϯμϦϯάͰݺͼग़͢ࡍʹ౉͞ΕΔ // ίϯςΩετΛड͚औΔؔ਺Λ export ͢Δ // ؔ਺͸ Vue ΠϯελϯεΛฦ͢ඞཁ͕͋Δ export default context => { // σʔλͷϑΣον return app.fetchServerData(context.url).then(() => { return app }) }
  32. ίϯςΩετͷಋೖ • ϦΫΤετϋϯυϥ಺ͰίϯςΩετΛ࡞੒͠ɺϨϯμ ϥͷϨϯμϦϯάϝιουʹࢦఆ࣮ͯ͠ߦ // for express app.get('*', (req, res)

    => { const context = { url: req.url } const renderStream = renderer.renderToStream(context) res.write(html.head) renderStream.on('data', chunk => { ... res.write(chunk) }) renderStream.on('end', () => { res.end(html.tail) }) }) ϨϯμϦϯάͷࡍʹ༻ҙͨ͠Τ ϯτϦϙΠϯτ͕ݺ͹Εͯɺί ϯςΩετ͕౉͞ΕΔ
  33. αʔό޲͚ͷόϯυϧԽ (webpackͷྫ) • αʔό޲͚ʹɺґଘϞδϡʔϧΛ֎෦Խ͢ΔΑ͏ʹόϯ υϧԽͷઃఆΛ͢Δ // webpack ͷઃఆ module.exports =

    { entry: './src/server-entry.js', target: 'node', output: { ..., libraryTarget: 'commonjs2' }, // webpack ͷ externals Λར༻ͯ͠ɺpackage.json ͷ "dependencies"ͷ ݩʹ // ͋ΔϞδϡʔϧΛશͯ֎෦Խ externals: Object.keys(require('./package.json').dependencies) }
  34. ϥΠϑαΠΫϧϑοΫ • beforeCreate • created
 
 
 
 
 


    
 
 
 
 
 • beforeMount • mounted • beforeUpdate • updated • activated • deactivated • beforeDestroy • destroyed ݺ͹ΕΔϑοΫ ݺ͹Εͳ͍ϑοΫ
  35. ϥΠϑαΠΫϧϑοΫҎ֎ • Ͳͷ API ͕αʔόαΠυͰಈ࡞͢Δ͔Ͳ͏͔͸ɺݫີʹ ͸ॻ͔Ε͍ͯͳ͍ • DOM ʹґଘ͍ͯ͠ͳ͍ܥ౷ͷ΋ͷ͸ɺಈ࡞͢Δ͸ͣ •

    Πϕϯτ: $emit, $off, $on, $once • σʔλܥ: $watch, $set, $delete, $data, ͳͲ • ͦͷଞ΋Ζ΋Ζ …