|
| 1 | +# ビルド設定 |
| 2 | + |
| 3 | +クライアントサイドで完結するプロジェクトのwebpack設定は既に知っての通りでしょう。 SSRプロジェクトにおいても大枠は似たようなものですが、設定ファイルを3つのファイル(*base*,*client*,*server*)に分けることを提案しています。base設定は出力パス、エイリアス、ローダーのような、clientとserver両方の環境に共有される設定を含み、server設定とclient設定は単純に、 [webpack-merge](https://github.com/survivejs/webpack-merge)を使って、base設定を拡張することができるものです。 |
| 4 | + |
| 5 | +## server設定 |
| 6 | + |
| 7 | +server設定は`createBundleRenderer`に渡されるサーババンドルを生成するために作られるもので、次のようになります: |
| 8 | + |
| 9 | +```js |
| 10 | +const merge = require('webpack-merge') |
| 11 | +const nodeExternals = require('webpack-node-externals') |
| 12 | +const baseConfig = require('./webpack.base.config.js') |
| 13 | +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') |
| 14 | +module.exports = merge(baseConfig, { |
| 15 | + // Point entry to your app's server entry file |
| 16 | + entry: '/path/to/entry-server.js', |
| 17 | + // This allows webpack to handle dynamic imports in a Node-appropriate |
| 18 | + // fashion, and also tells `vue-loader` to emit server-oriented code when |
| 19 | + // compiling Vue components. |
| 20 | + target: 'node', |
| 21 | + // For bundle renderer source map support |
| 22 | + devtool: 'source-map', |
| 23 | + // This tells the server bundle to use Node-style exports |
| 24 | + output: { |
| 25 | + libraryTarget: 'commonjs2' |
| 26 | + }, |
| 27 | + // https://webpack.js.org/configuration/externals/#function |
| 28 | + // https://github.com/liady/webpack-node-externals |
| 29 | + // Externalize app dependencies. This makes the server build much faster |
| 30 | + // and generates a smaller bundle file. |
| 31 | + externals: nodeExternals({ |
| 32 | + // do not externalize dependencies that need to be processed by webpack. |
| 33 | + // you can add more file types here e.g. raw *.vue files |
| 34 | + // you should also whitelist deps that modifies `global` (e.g. polyfills) |
| 35 | + whitelist: /\.css$/ |
| 36 | + }), |
| 37 | + // This is the plugin that turns the entire output of the server build |
| 38 | + // into a single JSON file. The default file name will be |
| 39 | + // `vue-ssr-server-bundle.json` |
| 40 | + plugins: [ |
| 41 | + new VueSSRServerPlugin() |
| 42 | + ] |
| 43 | +}) |
| 44 | +``` |
| 45 | + |
| 46 | + `vue-ssr-server-bundle.json`が生成されたら、ファイルパスを `createBundleRenderer`に渡します: |
| 47 | + |
| 48 | +```js |
| 49 | +const { createBundleRenderer } = require('vue-server-renderer') |
| 50 | +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { |
| 51 | + // ...other renderer options |
| 52 | +}) |
| 53 | +``` |
| 54 | + |
| 55 | +別の方法として、 バンドルをオブジェクトとして`createBundleRenderer`に渡すことも可能で、これは開発中のホットリロードに対して便利です。 参考として [HackerNewsの設定](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js) を見てみてください。 |
| 56 | + |
| 57 | +### externalsの注意 |
| 58 | + |
| 59 | + CSSファイルを`externals`オプションにホワイトリスト登録していることに注目してください。その理由は、依存関係からインポートされるCSS はwebpackによって処理されないといけないからです。 もし同じようにwebpackに依存する他のタイプのファイルをインポートしているなら、 (例: `*.vue`, `*.sass`)、 それらも同じようにホワイトリストに加えなければいけません。 |
| 60 | + |
| 61 | +ホワイトリスト登録する他のタイプのモジュールは、例えば `babel-polyfill`のような`global`を修正するポリフィルです。なぜなら、サーババンドルの中のコードは独自の ** `global` **オブジェクトを持っているからです。Node7.6以降を使っていればサーバに`babel-polyfill`はあまり必要ないので、単純にクライアントエントリーにインポートする方が簡単です。 |
| 62 | + |
| 63 | +## client設定 |
| 64 | + |
| 65 | +client設定はbase設定とほぼ同じままです。言うまでもなく、クライアント側のエントリーファイルに`entry`を示す必要があります。またそれとは別に、もし`CommonsChunkPlugin`使っていたら、それがclient設定だけで使われていることを確認しておかないといけません。なぜなら、サーババンドルは単一のエントリーチャンクを要求するからです。 |
| 66 | + |
| 67 | +### `clientManifest` の作成 |
| 68 | + |
| 69 | +> 必須 version 2.3.0以降 |
| 70 | +
|
| 71 | +サーババンドルに加えて、クライアントビルドマニフェストを作成することもできます。レンダラーは、クライアントマニフェストとサーババンドルでサーバ側*と*クライアント側の両方のビルド情報を持つことになり、 レンダリングされたHTMLに[preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/)やCSSのlinkやscriptタグを自動的に挿入することができます。 |
| 72 | + |
| 73 | +これには2重の恩恵があります: |
| 74 | + |
| 75 | +1. 生成されたファイル名にハッシュがある時に、正しいURLを注入する`html-webpack-plugin` の代替になります。 |
| 76 | +2. webpackのオンデマンドコード分割機能(code spliting)を利用するバンドルをレンダリングする時に、最適なチャンクがpreloaded / prefetchedされるのを保証でき、かつ、クライアントに対するウォーターフォールリクエストを避けるために、必要な非同期チャンクに`<script></script>`タグを挿入することができます。そのようにしてTTI (time-to-interactive)が改善します。 |
| 77 | + |
| 78 | +クライアントマニフェストを利用するためには、client設定はこのようになります: |
| 79 | + |
| 80 | +```js |
| 81 | +const webpack = require('webpack') |
| 82 | +const merge = require('webpack-merge') |
| 83 | +const baseConfig = require('./webpack.base.config.js') |
| 84 | +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') |
| 85 | +module.exports = merge(baseConfig, { |
| 86 | + entry: '/path/to/entry-client.js', |
| 87 | + plugins: [ |
| 88 | + // Important: this splits the webpack runtime into a leading chunk |
| 89 | + // so that async chunks can be injected right after it. |
| 90 | + // this also enables better caching for your app/vendor code. |
| 91 | + new webpack.optimize.CommonsChunkPlugin({ |
| 92 | + name: "manifest", |
| 93 | + minChunks: Infinity |
| 94 | + }), |
| 95 | + // This plugins generates `vue-ssr-client-manifest.json` in the |
| 96 | + // output directory. |
| 97 | + new VueSSRClientPlugin() |
| 98 | + ] |
| 99 | +}) |
| 100 | +``` |
| 101 | + |
| 102 | +これで、作成されたクライアントマニフェストをページテンプレートと一緒に利用できるようになります。 |
| 103 | + |
| 104 | +```js |
| 105 | +const { createBundleRenderer } = require('vue-server-renderer') |
| 106 | +const template = require('fs').readFileSync('/path/to/template.html', 'utf-8') |
| 107 | +const serverBundle = require('/path/to/vue-ssr-server-bundle.json') |
| 108 | +const clientManifest = require('/path/to/vue-ssr-client-manifest.json') |
| 109 | +const renderer = createBundleRenderer(serverBundle, { |
| 110 | + template, |
| 111 | + clientManifest |
| 112 | +}) |
| 113 | +``` |
| 114 | + |
| 115 | +この設定で、コード分割されたビルドのためにサーバ側でレンダリングされるHTMLはこのようになります(すべて自動でインジェクトされます)。 |
| 116 | + |
| 117 | +```html |
| 118 | + |
| 119 | + |
| 120 | + <!-- chunks used for this render will be preloaded --> |
| 121 | + <link rel="preload" href="/manifest.js" as="script"> |
| 122 | + <link rel="preload" href="/main.js" as="script"> |
| 123 | + <link rel="preload" href="/0.js" as="script"> |
| 124 | + <!-- unused async chunks will be prefetched (lower priority) --> |
| 125 | + <link rel="prefetch" href="/1.js" as="script"> |
| 126 | + |
| 127 | + |
| 128 | + <!-- app content --> |
| 129 | + <div data-server-rendered="true"><div data-segment-id="282354">async</div></div> |
| 130 | + <!-- manifest chunk should be first --> |
| 131 | + <script src="/manifest.js"></script> |
| 132 | + <!-- async chunks injected before main chunk --> |
| 133 | + <script src="/0.js"></script> |
| 134 | + <script src="/main.js"></script> |
| 135 | + |
| 136 | +` |
| 137 | +``` |
| 138 | + |
| 139 | +### 手動でのアセットインジェクション |
| 140 | + |
| 141 | +デフォルト設定で、アセットインジェクションはあなたが作成した`template`レンダリングオプションで自動に行われます。 しかし、アセットがどのようにテンプレートにインジェクトされるかをより細かくコントロールしたい時もあるでしょうし、あるいはテンプレートを使わない時もあるかもしれません。そのような場合にはレンダラーを作る時に`inject: false`を渡せば、手動でアセットインジェクションを行うことができます。 |
| 142 | + |
| 143 | +渡した`context`オブジェクトは`renderToString`コールバックで、 次のメソッドを持ちます: |
| 144 | + |
| 145 | +- `context.renderStyles()` |
| 146 | + |
| 147 | +これは、レンダリング中に使われた`*.vue`コンポーネントから集めた全てのクリティカルCSSを含んだ`<style></style>` タグを返します。詳細は [CSS Management](./css.md)の章を見てください。 |
| 148 | + |
| 149 | +もし `clientManifest` が提供されたら、返ってきたストリングはwebpackが放出したCSSファイルの`<link rel="stylesheet">`タグも含みます。 (例 : `extract-text-webpack-plugin`から抽出されたCSSや、`file-loader`でインポートされたCSS) |
| 150 | + |
| 151 | +- `context.renderState(options?: Object)` |
| 152 | + |
| 153 | + このメソッドは `context.state` をシリアライズし、 `window.__INITIAL_STATE__`ステートとして埋め込まれたインラインスクリプトを返します。 |
| 154 | + |
| 155 | +contextのステートキーとwindowのステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 |
| 156 | + |
| 157 | +```js |
| 158 | + context.renderState({ |
| 159 | + contextKey: 'myCustomState', |
| 160 | + windowKey: '__MY_STATE__' |
| 161 | + }) |
| 162 | + // -> <script>window.__MY_STATE__={...}</script> |
| 163 | +``` |
| 164 | + |
| 165 | +- `context.renderScripts()` |
| 166 | + - 必須 `clientManifest` |
| 167 | + |
| 168 | +このメソッドはクライアントアプリケーションを起動するのに必要な `<script></script>` タグを返します。コードの中に非同期コード分割を使っている時、このメソッドは賢くも、インクルードされるべき正しい非同期チャンクを推論します。 |
| 169 | + |
| 170 | +- `context.renderResourceHints()` |
| 171 | + - 必須 `clientManifest` |
| 172 | + |
| 173 | +このメソッドは、現在レンダリングされているページに必要な`<link rel="preload/prefetch">` リソースヒントを返します。 デフォルト設定ではこのようになります: |
| 174 | + |
| 175 | +- ページに必要なJavaScriptやCSSファイルをプリロードする |
| 176 | +- あとで必要な非同期JavaScriptチャンクをプリフェッチする |
| 177 | + |
| 178 | + ファイルのプリロードは[`shouldPreload`](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 |
| 179 | + |
| 180 | +- `context.getPreloadFiles()` |
| 181 | + - 必須 `clientManifest` |
| 182 | + |
| 183 | +このメソッドは stringを返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2サーバプッシュをプログラムで行うときに使えるでしょう。 |
| 184 | + |
| 185 | +`createBundleRenderer`に渡された`template`は`context`を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: |
| 186 | + |
| 187 | +```html |
| 188 | + |
| 189 | + |
| 190 | + <!-- use triple mustache for non-HTML-escaped interpolation --> |
| 191 | + {{{ renderResourceHints() }}} |
| 192 | + {{{ renderStyles() }}} |
| 193 | + |
| 194 | + |
| 195 | + <!--vue-ssr-outlet--> |
| 196 | + {{{ renderState() }}} |
| 197 | + {{{ renderScripts() }}} |
| 198 | + |
| 199 | + |
| 200 | +``` |
| 201 | + |
| 202 | +もし `template` を全く使っていないのなら、自分自身でstringを結合することができます。 |
0 commit comments