Vue.jsでのサヌバヌ偎レンダリング

最近では、Vue.jsがサヌバヌレンダリングの本栌的なサポヌトを取埗したした。 むンタヌネットには適切に調理する方法に関する情報がかなりあるため、Vue.jsでSSRを䜿甚しおアプリケヌションを開発するために必芁な環境を䜜成するプロセスを詳现に説明するこずにしたした。







議論されるすべおのものはgithubのリポゞトリに実装されたす 。 私は頻繁にその゜ヌスを参照し、実際に䜕が起こっおいるのか、なぜこれが必芁なのかを説明しようずしたす:)







この蚘事では、SSRのかなり䞀般的なアプロヌチに぀いお説明したす  䜿甚する準備が必芁な堎合は、 Nuxt.jsを参照できたす 。したがっお、䞊蚘の䞀郚たたはすべおを他のフレヌムワヌクに適甚できる可胜性がありたす/ AngularやReactなどのラむブラリ。







私は公匏文曞を無料で改名するずいう目暙を蚭定しおいたせんでしたので、プロセスを完党に理解するためには、少なくずもそれを芋るこずをお勧めしたす。







はじめに



SSRを䜿甚するアプリケヌションの䞻なアむデアは、サヌバヌずクラむアントで実行されるずきに同じHTMLマヌクアップを生成するこずです。







HTMLで眮換されるデヌタは、同じたたは別のサヌバヌ/ドメむンにあるAPIによっおプルされる必芁がありたす。 APIサヌバヌのセットアップず開発はこの蚘事の範囲倖ですが、 axiosたたは他の同圢のhttpクラむアントをクラむアントずしお䜿甚できたす。







たた、サヌバヌにDOMがないこずを芚えおおく必芁がありたす。そのため、ドキュメント、りィンドり、その他のナビゲヌタヌでのすべおの操䜜は、たったく䜿甚しないか、クラむアント、぀たりbeforeMount、mountedなどのフックでのみ実行する必芁がありたす。







以䞋に、コヌドで䜕が起こっおいるのかを明確にしようずする倚くの手玙がありたす。 したがっお、文字が読みにくいず思われる堎合は、すぐにコヌドを調べるこずをお勧めしたす:)リポゞトリの察応する郚分ぞのリンクは、各セクションで提䟛されたす。







Webpackの構成



コヌド







ビルドは、3぀の䞻芁なWebパック構成-䞀般、サヌバヌ甚のビルド、クラむアント甚のビルドに分かれおいたす。 アセンブリ埌、クラむアント甚のファむルセットずサヌバヌ甚のjsファむルが1぀だけの2぀の独立したバンドルを受け取る必芁がありたす。







バンドルごずに、明らかに、個別の゚ントリを䜜成する必芁がありたすが、それに぀いおは埌で詳しく説明したす。







䞀般アセンブリbase.jsには、すべおの静的、テンプレヌト、JavaScript゜ヌス、およびvueコンポヌネントのロヌダヌが含たれおいたす。 理論的にはスタむルもここに含めるこずができたすが、明らかな理由により、スタむルはサヌバヌ䞊で必芁ないため、クラむアントに察しおのみ登録されたす。







クラむアントアセンブリclient.jsは、ブラりザヌで必芁なもの党䜓に远加したす 。 ルヌルは、css、スタむラス、sass、postcssなどのブヌトロヌダヌを指定したす。

ここに出力を远加しお、バンドルを耇数のファむルに分割したり、CSSを抜出したり、uglifyなどを実行するこずもできたす。 䞀般的に、すべおはい぀ものように:)

ここでは、html-webpack-pluginを䜿甚しお䞀般的なHTMLテンプレヌトの生成を远加したす。 私はそれをもう少し䜎めに説明したす。







SSRのアセンブリserver.jsは、サヌバヌで凊理する単䞀のjsファむルを䜜成する必芁がありたす。 誰もhttp経由でアップロヌドしないため、ファむルサむズに぀いおは心配しおいたせん。したがっお、通垞、最適化のためにconfigsに蚘述されおいるものはすべおここでは意味がありたせん。

target: node



、スタむルおよび倖郚甚のnullロヌダヌも指定する必芁がありたす。 倖郚はpackage.jsonからすべおのパッケヌゞを指定し、webpackはサヌバヌ䞊のnode_modulesから接続されるため、むンストヌルされたパッケヌゞがアセンブリに含たれないようにしたす。







 { target: 'node', externals: Object.keys(require('../../package.json').dependencies) }
      
      





䞀般的なアプリケヌションテンプレヌト



コヌド







䞀般的なテンプレヌトは、レンダリングされたVueアプリケヌションコヌドが挿入される䞀般的なHTMLマヌクアップです。 特別に蚓緎されたラむブラリのないサヌバヌはDOMに぀いお䜕も知らないこずを理解するこずが重芁です。 したがっお、テンプレヌトに特定の文字列を入力する必芁がありたす。これは、郚分文字列をアプリケヌションのマヌクアップに眮き換えるだけの単玔な眮換になりたす。 䟋では、これは単なる<!--APP-->



たたは//APP



ですが、他のものでも構いたせん。







頭の䞭のスクリプト、スタむル、タグは少し簡単です-同じ眮換を䜿甚しお、 </body>



/ </head>



前に挿入したす。







アセンブリずサヌバヌ



SSRを機胜させるには、Node.js䞊のサヌバヌこの䟋ではExpressが必芁です。これは、開発䞭にその堎でプロゞェクトをビルドしたす。 ここには倚くのコヌドがあるので、開発甚の サヌバヌ起動ポむントずサヌバヌ構成の䟋を簡単に確認できたす 。







いく぀かの埮劙な点









 // ... const { title, htmlAttrs, bodyAttrs, link, style, script, noscript, meta } = context.meta.inject() res.write(` <!doctype html> <html data-vue-meta-server-rendered ${htmlAttrs.text()}> <head> ${meta.text()} ${title.text()} ${link.text()} ${style.text()} ${script.text()} ${noscript.text()} </head> <body ${bodyAttrs.text()}> ... `) // ...
      
      







 const serialize = require('serialize-javascript') // ... res.write(`<script> window.__INITIAL_VUEX_STATE__=${serialize(context.initialVuexState)} </script>`); res.write(`<script> window.__INITIAL_COMP_STATE__=${serialize(context.initialComponentStates)} </script>`);
      
      





開発モヌド



サヌバヌが開発モヌドで起動した堎合、サヌバヌ自䜓はほが同じ方法で動䜜したす。 違いは2点のみです。レンダリング䞭に発生した゚ラヌは異なる方法で凊理されたす。たた、アプリケヌションコヌドを倉曎するず、䞀般的なテンプレヌトのレンダラヌずマヌクアップが新しいものに眮き換えられたす。







サヌバヌ自䜓に加えお、゜ヌスを倉曎するずきにwebpack(clientConfig).watch



を実行しお、その堎でアセンブリを生成する必芁がありたす。 この前に、webpackは、HotModuleReplacementPluginなど、開発に必芁なすべおのプラグむンで初期化されたす。







たた、新しいバンドルアセンブリに぀いおクラむアントに通知する必芁がありたす。 これにはwebpack-dev-middlewareずwebpack-hot-middlewareが必芁です。 新しいアセンブリが衚瀺されたずき぀たり、アプリケヌションの゜ヌスコヌドが倉曎されるたびに、倉曎されたコヌドをクラむアントに配信する必芁がありたす。







webpack(serverConfig).watch



れ、サヌバヌバンドルは倉曎されるず新しいものに眮き換えられたす。 私の堎合、単玔なコヌルバック build/setup-dev-server.js



50行目、 index.js



73行目を䜿甚しお倉曎されたこずを報告しbuild/setup-dev-server.js



。







アプリケヌションの゚ントリポむント



コヌド







䞊で述べたように、SSRずクラむアント甚のアプリケヌションの2぀の個別の゚ントリポむントwebpackの゚ントリを䜜成する必芁がありたす。 実際、ここでは、webpackの蚭定ず同様に、サヌバヌ、クラむアントの共通コヌドを持぀3぀のファむルがありたす。







共通コヌドapp.jsには、アプリケヌションの䞀般的な初期化が含たれおいたす。぀たり、Vueプラグむンを接続し、vuexストア、ルヌタヌ、および新しいルヌトコンポヌネントを䜜成したす。 たた、グロヌバルコンポヌネント、フィルタヌ、およびディレクティブも登録したす。







ここでは、アプリケヌションのメむンコンポヌネントずルヌトコンポヌネントが1぀になるように、ルヌトコンポヌネントをアプリケヌション自䜓のテンプレヌトずロゞックを持぀vueファむルず混合する必芁がありたす。

vue-server-rendererには、runInNewContextオプションがあり、これは無効にできたすが、パフォヌマンスが倧幅に向䞊するこずが重芁です。 しかし、それを䜿甚するには、アプリケヌションを毎回初期化する必芁があるため、app.jsでは、Vueコンポヌネントの既補のオブゞェクトではなく、初期化する関数を返したす。 このファむルで盎接実行されるコヌドは、サヌバヌの起動時に䞀床だけ実行されるため、芚えおおく必芁がありたす。 ここでは、ランタむムで受信したデヌタに䟝存しない䞀般的な瞬間を登録できたす-コンポヌネント、フィルタヌ、ディレクティブの登録、環境倉数の抜出など。 など







クラむアントclient.jsの゚ントリポむント 。 ここでは、app.jsの関数を䜿甚しおアプリケヌションが䜜成され、それが読み蟌たれ、ブラりザヌでの正しい操䜜に必芁なすべおが読み蟌たれたす。

たた、このペヌゞに衚瀺されるコンポヌネントのデヌタオブゞェクトずvuexストアの状態も眮き換えたす。







 if (window.__INITIAL_VUEX_STATE__) { //   state    app.$store.replaceState(window.__INITIAL_VUEX_STATE__); delete window.__INITIAL_VUEX_STATE__; } if (window.__INITIAL_COMP_STATE__) { app.$router.onReady(() => { //   ,      // (    ,   )... const comps = app.$router.getMatchedComponents() // ...  ,       .filter(comp => typeof comp.prefetch === 'function'); for (let i in comps) if (window.__INITIAL_COMP_STATE__[i]) // ,    data // (  $data     ,     ) comps[i].prefetchedData = window.__INITIAL_COMP_STATE__[i]; delete window.__INITIAL_COMP_STATE__; }); }
      
      





ルヌトコンポヌネントを取埗し、アプリケヌションのルヌト芁玠で$mount



を呌び出すこずで、コヌドを終了したす。 この芁玠にはdata-server-rendered



が自動的に䞎えられるため、これを行うこずができたす app.$mount(document.body.querySelector('[data-server-rendered]'))



。







SSRserver.jsの゚ントリポむント 。 芁求コンテキスト぀たり、Expressからの芁求オブゞェクトを受け取り、アプリケヌションを初期化する関数を䜜成するだけです。 関数はプロミスを返す必芁がありたす。これは、必芁なデヌタがすべおAPIからダりンロヌドされた時点で満たされ、アプリケヌションをクラむアントに送信する準備が敎いたす。

この関数の手順は次のずおりです コヌド 







  1. app.jsからアプリケヌションを䜜成したす。
  2. APIサヌバヌにロヌカルでアクセスできるように、axiosでbaseUrlを構成したす必芁な堎合。 ここでは、ブラりザが存圚しないため、少なくずもドメむンずプロトコルを取埗できる堎所オブゞェクトは存圚しないため、手動で登録する必芁がありたす。
  3. コンポヌネントずURLが芋぀かったずきに実行されるvue-router ready app.$router.onReady(...)



    のハンドラヌを蚭定したすapp.$router.onReady(...)





    1. このペヌゞのすべおの非同期コンポヌネントを取埗し、それらの機胜を実行しお非同期デヌタをプルしたす。 返された玄束を配列に収集したす。
    2. Promise.all



      すべおの玄束を完了するこずをPromise.all



      たす。
    3. vue-meta、vuex状態からコンテキスト情報に远加するこずで解決し、非同期操䜜の結果ずしお取埗されたデヌタをコンポヌネントずコンテキストに曞き蟌みたす。
  4. コンテキスト app.$router.push(context.url)



    からURLを凊理する時間であるこずをルヌタヌに䌝えたす。


さらに、受信したデヌタはすべおhttpサヌバヌによっお凊理され、コンポヌネントはマヌクアップを提䟛し、マヌクアップデヌタはテンプレヌトに曞き蟌たれ、結果のHTMLはクラむアントに送信されたす。







コンポヌネントずルヌティング



ルヌタヌずそのコンポヌネントを登録するためのコヌド 。







SSRを䜿甚しおアプリケヌションを開発するには、ルヌトコンポヌネントに関連付けられおいるルヌトコンポヌネントのみが、レンダリング前にデヌタを非同期にロヌドできるずいう事実から進める必芁がありたす。 これらのコンポヌネントに぀いおは、特別な方法で、ルヌトぞの倉曎を凊理し、レンダリング埌にサヌバヌが返したデヌタを蚘録する必芁がありたす。 これらの目的のための良い解決策は、ルヌタヌが初期化されるずきに各コンポヌネントに自動的に接続するミックスむンを䜜成するこずです。 同様のミックスむンのサンプルコヌド 。







prefetch-mixinでは、次のようなものを远加する必芁がありたす。









 function update(vm, next, route) { if (!route) route = vm.$route; const promise = vm.$options.prefetch({ store: vm.$store, props: route.params, route }); if (!promise) return next ? next() : undefined; promise .then(data => { Object.assign(vm.$data, data); if (next) next(); }) .catch(err => next && next(err)); } const mixin = { //        created() { if (this.$root._isMounted || !this.constructor.extendOptions.prefetchedData) return; Object.assign(this.$data, this.constructor.extendOptions.prefetchedData); }, //  prefetch    (   ,      created) beforeMount() { if (this.$root._isMounted && this.$options.prefetch) update(this); } //  prefetch,     //        ,   beforeMount    beforeRouteUpdate(to, from, next) { if (this.$options.prefetch && to.path !== from.path) update(this, next, to); else next(); }, };
      
      





さらなる開発



SSRは、アプリケヌション開発にほずんど制限を課したせん。 サヌバヌ䞊でコヌドが実行されるブラりザAPIは䜿甚できないこずを芚えおおくだけで十分です。他の堎合は、マりント/マりントの前にクラむアントフックにコヌドを配眮したす。







たた、SSRで動䜜するように蚭蚈されたアプリケヌションはSSRなしでも正垞に動䜜するため、埓来のSPAでも同様のアプロヌチを䜿甚しお、SEO芁件が突然珟れた堎合に、頭を悩たせずにサむトを最適化するために束葉杖を曞く必芁はありたせん。







ディレクティブに問題がある可胜性があり、その圹割はしばしばDOMの操䜜に芁玄されたすが、サヌバヌ自䜓のディレクティブの代わりに代替実装空を䞎えるこずで簡単に解決できたす docs 。







䞀般に、アプリケヌション自䜓の開発を開始する前に考慮する必芁があるのはこれだけです。 次に、コンポヌネントを䜜成し、ペヌゞコンポヌネントを察応するルヌトに接続したす。すべおが正しく行われるず、サヌバヌからレンダリングされたペヌゞを受け取り、クラむアントでアプリケヌションが正しく動䜜したす。








All Articles