Vue.js SSR 構(gòu)建配置

2021-01-07 15:37 更新

我們假設(shè)你已經(jīng)知道,如何為純客戶(hù)端 (client-only) 項(xiàng)目配置 webpack。服務(wù)器端渲染 (SSR) 項(xiàng)目的配置大體上與純客戶(hù)端項(xiàng)目類(lèi)似,但是我們建議將配置分為三個(gè)文件:base, clientserver?;九渲?(base config) 包含在兩個(gè)環(huán)境共享的配置,例如,輸出路徑 (output path),別名 (alias) 和 loader。服務(wù)器配置 (server config) 和客戶(hù)端配置 (client config),可以通過(guò)使用 webpack-merge 來(lái)簡(jiǎn)單地?cái)U(kuò)展基本配置。

服務(wù)器配置 (Server Config)

服務(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)處理動(dòng)態(tài)導(dǎo)入(dynamic import),
  // 并且還會(huì)在編譯 Vue 組件時(shí),
  // 告知 `vue-loader` 輸送面向服務(wù)器代碼(server-oriented code)。
  target: 'node',


  // 對(duì) 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)用程序依賴(lài)模塊??梢允狗?wù)器構(gòu)建速度更快,
  // 并生成較小的 bundle 文件。
  externals: nodeExternals({
    // 不要外置化 webpack 需要處理的依賴(lài)模塊。
    // 你可以在這里添加更多的文件類(lèi)型。例如,未處理 *.vue 原始文件,
    // 你還應(yīng)該將修改 `global`(例如 polyfill)的依賴(lài)模塊列入白名單
    whitelist: /\.css$/
  }),


  // 這是將服務(wù)器的整個(gè)輸出
  // 構(gòu)建為單個(gè) 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 的其他選項(xiàng)
})

又或者,你還可以將 bundle 作為對(duì)象傳遞給 createBundleRenderer。這對(duì)開(kāi)發(fā)過(guò)程中的熱重載是很有用的 - 具體請(qǐng)查看 HackerNews demo 的參考設(shè)置

擴(kuò)展說(shuō)明 (Externals Caveats)

請(qǐng)注意,在 externals 選項(xiàng)中,我們將 CSS 文件列入白名單。這是因?yàn)閺囊蕾?lài)模塊導(dǎo)入的 CSS 還應(yīng)該由 webpack 處理。如果你導(dǎo)入依賴(lài)于 webpack 的任何其他類(lèi)型的文件(例如 *.vue, *.sass),那么你也應(yīng)該將它們添加到白名單中。

如果你使用 runInNewContext: 'once'runInNewContext: true,那么你還應(yīng)該將修改 global 的 polyfill 列入白名單,例如 babel-polyfill。這是因?yàn)楫?dāng)使用新的上下文模式時(shí),server bundle 中的代碼具有自己的 global 對(duì)象。由于在使用 Node 7.6+ 時(shí),在服務(wù)器并不真正需要它,所以實(shí)際上只需在客戶(hù)端 entry 導(dǎo)入它。

客戶(hù)端配置 (Client Config)

客戶(hù)端配置 (client config) 和基本配置 (base config) 大體上相同。顯然你需要把 entry 指向你的客戶(hù)端入口文件。除此之外,如果你使用 CommonsChunkPlugin,請(qǐng)確保僅在客戶(hù)端配置 (client config) 中使用,因?yàn)榉?wù)器包需要單獨(dú)的入口 chunk。

生成 clientManifest

需要版本 2.3.0+

除了 server bundle 之外,我們還可以生成客戶(hù)端構(gòu)建清單 (client build manifest)。使用客戶(hù)端清單 (client manifest) 和服務(wù)器 bundle(server bundle),renderer 現(xiàn)在具有了服務(wù)器和客戶(hù)端的構(gòu)建信息,因此它可以自動(dòng)推斷和注入資源預(yù)加載 / 數(shù)據(jù)預(yù)取指令(preload / prefetch directive) ,以及 css 鏈接 / script 標(biāo)簽到所渲染的 HTML。

好處是雙重的:

  1. 在生成的文件名中有哈希時(shí),可以取代 html-webpack-plugin 來(lái)注入正確的資源 URL。

  1. 在通過(guò) webpack 的按需代碼分割特性渲染 bundle 時(shí),我們可以確保對(duì) chunk 進(jìn)行最優(yōu)化的資源預(yù)加載/數(shù)據(jù)預(yù)取,并且還可以將所需的異步 chunk 智能地注入為 <script> 標(biāo)簽,以避免客戶(hù)端的瀑布式請(qǐng)求 (waterfall request),以及改善可交互時(shí)間 (TTI - time-to-interactive)。

要使用客戶(hù)端清單 (client manifest),客戶(hù)端配置 (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 運(yùn)行時(shí)分離到一個(gè)引導(dǎo) chunk 中,
    // 以便可以在之后正確注入異步 chunk。
    // 這也為你的 應(yīng)用程序/vendor 代碼提供了更好的緩存。
    new webpack.optimize.CommonsChunkPlugin({
      name: "manifest",
      minChunks: Infinity
    }),
    // 此插件在輸出目錄中
    // 生成 `vue-ssr-client-manifest.json`。
    new VueSSRClientPlugin()
  ]
})

然后,你就可以使用生成的客戶(hù)端清單 (client manifest) 以及頁(yè)面模板:

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
})

通過(guò)以上設(shè)置,使用代碼分割特性構(gòu)建后的服務(wù)器渲染的 HTML 代碼,將看起來(lái)如下(所有都是自動(dòng)注入):

<html>
  <head>
    <!-- 用于當(dāng)前渲染的 chunk 會(huì)被資源預(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 會(huì)被數(shù)據(jù)預(yù)取(prefetch)(次要優(yōu)先級(jí)) -->
    <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>

手動(dòng)資源注入(Manual Asset Injection)

默認(rèn)情況下,當(dāng)提供 template 渲染選項(xiàng)時(shí),資源注入是自動(dòng)執(zhí)行的。但是有時(shí)候,你可能需要對(duì)資源注入的模板進(jìn)行更細(xì)粒度 (finer-grained) 的控制,或者你根本不使用模板。在這種情況下,你可以在創(chuàng)建 renderer 并手動(dòng)執(zhí)行資源注入時(shí),傳入 inject: false

renderToString 回調(diào)函數(shù)中,你傳入的 context 對(duì)象會(huì)暴露以下方法:

  • context.renderStyles()

這將返回內(nèi)聯(lián) <style> 標(biāo)簽包含所有關(guān)鍵 CSS(critical CSS) ,其中關(guān)鍵 CSS 是在要用到的 *.vue 組件的渲染過(guò)程中收集的。有關(guān)更多詳細(xì)信息,請(qǐng)查看 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 并返回一個(gè)內(nèi)聯(lián)的 script,其中狀態(tài)被嵌入在 window.__INITIAL_STATE__ 中。

上下文狀態(tài)鍵 (context state key) 和 window 狀態(tài)鍵 (window state key),都可以通過(guò)傳遞選項(xiàng)對(duì)象進(jìn)行自定義:

  context.renderState({
    contextKey: 'myCustomState',
    windowKey: '__MY_STATE__'
  })


  // -> <script>window.__MY_STATE__={...}</script>

  • context.renderScripts()

  • 需要 clientManifest

此方法返回引導(dǎo)客戶(hù)端應(yīng)用程序所需的 <script> 標(biāo)簽。當(dāng)在應(yīng)用程序代碼中使用異步代碼分割 (async code-splitting) 時(shí),此方法將智能地正確的推斷需要引入的那些異步 chunk。

  • context.renderResourceHints()

  • 需要 clientManifest

此方法返回當(dāng)前要渲染的頁(yè)面,所需的 <link rel="preload/prefetch"> 資源提示 (resource hint)。默認(rèn)情況下會(huì):

  • 預(yù)加載頁(yè)面所需的 JavaScript 和 CSS 文件
  • 預(yù)取異步 JavaScript chunk,之后可能會(huì)用于渲染

使用 shouldPreload 選項(xiàng)可以進(jìn)一步自定義要預(yù)加載的文件。

  • context.getPreloadFiles()

  • 需要 clientManifest

此方法不返回字符串 - 相反,它返回一個(gè)數(shù)組,此數(shù)組是由要預(yù)加載的資源文件對(duì)象所組成。這可以用在以編程方式 (programmatically) 執(zhí)行 HTTP/2 服務(wù)器推送 (HTTP/2 server push)。

由于傳遞給 createBundleRenderertemplate 將會(huì)使用 context 對(duì)象進(jìn)行插值,你可以(通過(guò)傳入 inject: false)在模板中使用這些方法:

<html>
  <head>
    <!-- 使用三花括號(hào)(triple-mustache)進(jìn)行 HTML 不轉(zhuǎn)義插值(non-HTML-escaped interpolation) -->
    {{{ renderResourceHints() }}}
    {{{ renderStyles() }}}
  </head>
  <body>
    <!--vue-ssr-outlet-->
    {{{ renderState() }}}
    {{{ renderScripts() }}}
  </body>
</html>

如果你根本沒(méi)有使用 template,你可以自己拼接字符串。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)