我們假設(shè)你已經(jīng)知道,如何為純客戶端 (client-only) 項目配置 webpack。服務(wù)器端渲染 (SSR) 項目的配置大體上與純客戶端項目類似,但是我們建議將配置分為三個文件:base, client 和 server。基本配置 (base config) 包含在兩個環(huán)境共享的配置,例如,輸出路徑 (output path),別名 (alias) 和 loader。服務(wù)器配置 (server config) 和客戶端配置 (client config),可以通過使用 webpack-merge 來簡單地擴(kuò)展基本配置。
服務(wù)器配置,是用于生成傳遞給 createBundleRenderer
的 server bundle。它應(yīng)該是這樣的:
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 將 entry 指向應(yīng)用程序的 server entry 文件
entry: '/path/to/entry-server.js',
// 這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動態(tài)導(dǎo)入(dynamic import),
// 并且還會在編譯 Vue 組件時,
// 告知 `vue-loader` 輸送面向服務(wù)器代碼(server-oriented code)。
target: 'node',
// 對 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 此處告知 server bundle 使用 Node 風(fēng)格導(dǎo)出模塊(Node-style exports)
output: {
libraryTarget: 'commonjs2'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化應(yīng)用程序依賴模塊??梢允狗?wù)器構(gòu)建速度更快,
// 并生成較小的 bundle 文件。
externals: nodeExternals({
// 不要外置化 webpack 需要處理的依賴模塊。
// 你可以在這里添加更多的文件類型。例如,未處理 *.vue 原始文件,
// 你還應(yīng)該將修改 `global`(例如 polyfill)的依賴模塊列入白名單
whitelist: /\.css$/
}),
// 這是將服務(wù)器的整個輸出
// 構(gòu)建為單個 JSON 文件的插件。
// 默認(rèn)文件名為 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
在生成 vue-ssr-server-bundle.json
之后,只需將文件路徑傳遞給 createBundleRenderer
:
const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
// ……renderer 的其他選項
})
又或者,你還可以將 bundle 作為對象傳遞給 createBundleRenderer
。這對開發(fā)過程中的熱重載是很有用的 - 具體請查看 HackerNews demo 的參考設(shè)置 。
請注意,在 externals
選項中,我們將 CSS 文件列入白名單。這是因為從依賴模塊導(dǎo)入的 CSS 還應(yīng)該由 webpack 處理。如果你導(dǎo)入依賴于 webpack 的任何其他類型的文件(例如 *.vue
, *.sass
),那么你也應(yīng)該將它們添加到白名單中。
如果你使用 runInNewContext: 'once'
或 runInNewContext: true
,那么你還應(yīng)該將修改 global
的 polyfill 列入白名單,例如 babel-polyfill
。這是因為當(dāng)使用新的上下文模式時,server bundle 中的代碼具有自己的 global
對象。由于在使用 Node 7.6+ 時,在服務(wù)器并不真正需要它,所以實際上只需在客戶端 entry 導(dǎo)入它。
客戶端配置 (client config) 和基本配置 (base config) 大體上相同。顯然你需要把 entry
指向你的客戶端入口文件。除此之外,如果你使用 CommonsChunkPlugin
,請確保僅在客戶端配置 (client config) 中使用,因為服務(wù)器包需要單獨的入口 chunk。
clientManifest
需要版本 2.3.0+
除了 server bundle 之外,我們還可以生成客戶端構(gòu)建清單 (client build manifest)。使用客戶端清單 (client manifest) 和服務(wù)器 bundle(server bundle),renderer 現(xiàn)在具有了服務(wù)器和客戶端的構(gòu)建信息,因此它可以自動推斷和注入資源預(yù)加載 / 數(shù)據(jù)預(yù)取指令(preload / prefetch directive) ,以及 css 鏈接 / script 標(biāo)簽到所渲染的 HTML。
好處是雙重的:
html-webpack-plugin
來注入正確的資源 URL。<script>
標(biāo)簽,以避免客戶端的瀑布式請求 (waterfall request),以及改善可交互時間 (TTI - time-to-interactive)。要使用客戶端清單 (client manifest),客戶端配置 (client config) 將如下所示:
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
entry: '/path/to/entry-client.js',
plugins: [
// 重要信息:這將 webpack 運行時分離到一個引導(dǎo) chunk 中,
// 以便可以在之后正確注入異步 chunk。
// 這也為你的 應(yīng)用程序/vendor 代碼提供了更好的緩存。
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// 此插件在輸出目錄中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
然后,你就可以使用生成的客戶端清單 (client manifest) 以及頁面模板:
const { createBundleRenderer } = require('vue-server-renderer')
const template = require('fs').readFileSync('/path/to/template.html', 'utf-8')
const serverBundle = require('/path/to/vue-ssr-server-bundle.json')
const clientManifest = require('/path/to/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(serverBundle, {
template,
clientManifest
})
通過以上設(shè)置,使用代碼分割特性構(gòu)建后的服務(wù)器渲染的 HTML 代碼,將看起來如下(所有都是自動注入):
<html>
<head>
<!-- 用于當(dāng)前渲染的 chunk 會被資源預(yù)加載(preload) -->
<link rel="preload" href="/manifest.js" as="script">
<link rel="preload" href="/main.js" as="script">
<link rel="preload" href="/0.js" as="script">
<!-- 未用到的異步 chunk 會被數(shù)據(jù)預(yù)取(prefetch)(次要優(yōu)先級) -->
<link rel="prefetch" href="/1.js" as="script">
</head>
<body>
<!-- 應(yīng)用程序內(nèi)容 -->
<div data-server-rendered="true"><div>async</div></div>
<!-- manifest chunk 優(yōu)先 -->
<script src="/manifest.js"></script>
<!-- 在主 chunk 之前注入異步 chunk -->
<script src="/0.js"></script>
<script src="/main.js"></script>
</body>
</html>
默認(rèn)情況下,當(dāng)提供 template
渲染選項時,資源注入是自動執(zhí)行的。但是有時候,你可能需要對資源注入的模板進(jìn)行更細(xì)粒度 (finer-grained) 的控制,或者你根本不使用模板。在這種情況下,你可以在創(chuàng)建 renderer 并手動執(zhí)行資源注入時,傳入 inject: false
。
在 renderToString
回調(diào)函數(shù)中,你傳入的 context
對象會暴露以下方法:
context.renderStyles()
這將返回內(nèi)聯(lián) <style>
標(biāo)簽包含所有關(guān)鍵 CSS(critical CSS) ,其中關(guān)鍵 CSS 是在要用到的 *.vue
組件的渲染過程中收集的。有關(guān)更多詳細(xì)信息,請查看 CSS 管理。
如果提供了 clientManifest
,返回的字符串中,也將包含著 <link rel="stylesheet">
標(biāo)簽內(nèi)由 webpack 輸出(webpack-emitted)的 CSS 文件(例如,使用 extract-text-webpack-plugin
提取的 CSS,或使用 file-loader
導(dǎo)入的 CSS)
context.renderState(options?: Object)
此方法序列化 context.state
并返回一個內(nèi)聯(lián)的 script,其中狀態(tài)被嵌入在 window.__INITIAL_STATE__
中。
上下文狀態(tài)鍵 (context state key) 和 window 狀態(tài)鍵 (window state key),都可以通過傳遞選項對象進(jìn)行自定義:
context.renderState({
contextKey: 'myCustomState',
windowKey: '__MY_STATE__'
})
// -> <script>window.__MY_STATE__={...}</script>
context.renderScripts()
clientManifest
此方法返回引導(dǎo)客戶端應(yīng)用程序所需的 <script>
標(biāo)簽。當(dāng)在應(yīng)用程序代碼中使用異步代碼分割 (async code-splitting) 時,此方法將智能地正確的推斷需要引入的那些異步 chunk。
context.renderResourceHints()
clientManifest
此方法返回當(dāng)前要渲染的頁面,所需的 <link rel="preload/prefetch">
資源提示 (resource hint)。默認(rèn)情況下會:
使用 shouldPreload
選項可以進(jìn)一步自定義要預(yù)加載的文件。
context.getPreloadFiles()
clientManifest
此方法不返回字符串 - 相反,它返回一個數(shù)組,此數(shù)組是由要預(yù)加載的資源文件對象所組成。這可以用在以編程方式 (programmatically) 執(zhí)行 HTTP/2 服務(wù)器推送 (HTTP/2 server push)。
由于傳遞給 createBundleRenderer
的 template
將會使用 context
對象進(jìn)行插值,你可以(通過傳入 inject: false
)在模板中使用這些方法:
<html>
<head>
<!-- 使用三花括號(triple-mustache)進(jìn)行 HTML 不轉(zhuǎn)義插值(non-HTML-escaped interpolation) -->
{{{ renderResourceHints() }}}
{{{ renderStyles() }}}
</head>
<body>
<!--vue-ssr-outlet-->
{{{ renderState() }}}
{{{ renderScripts() }}}
</body>
</html>
如果你根本沒有使用 template
,你可以自己拼接字符串。
更多建議: