Vue.js教程
8.9 构建配置
阅读(

Vue.js 基础

Vue.js 过渡和动画

Vue.js 可复用性和组合

Vue.js 工具

Vue.js 模块化

Vue.js 响应式系统原理

Vue.js API

Vue.js 服务器端渲染 SSR

简介

基本用法

编写通用代码

源码结构

路由和代码分割

数据预取和状态

客户端混合

Bundle Renderer 简介

构建配置

我们假设你已经知道,如何为纯客户端(client-only)项目配置 webpack。服务器端渲染(SSR)项目的配置大体上与纯客户端项目类似,但是我们建议将配置分为三个文件:base,client和server。基本配置(base config)包含在两个环境共享的配置,例如,输出路径(output path),别名(alias)和 loader。服务器配置(server config)和客户端配置(client config),可以通过使用webpack-merge来简单地扩展基本配置。

服务器配置(Server Config)

服务器配置,是用于生成传递给createBundleRenderer的 server bundle。它应该是这样的:

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 指向应用程序的 server entry 文件
  entry: '/path/to/entry-server.js',
  // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
  // 并且还会在编译 Vue 组件时,
  // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
  target: 'node',
  // 对 bundle renderer 提供 source map 支持
  devtool: 'source-map',
  // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
  output: {
    libraryTarget: 'commonjs2'
  },
  // https://webpack.js.org/configuration/externals/#function
  // https://github.com/liady/webpack-node-externals
  // 外置化应用程序依赖模块。可以使服务器构建速度更快,
  // 并生成较小的 bundle 文件。
  externals: nodeExternals({
    // 不要外置化 webpack 需要处理的依赖模块。
    // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
    // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
    whitelist: /\.css$/
  }),
  // 这是将服务器的整个输出
  // 构建为单个 JSON 文件的插件。
  // 默认文件名为 `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。这对开发过程中的热重新是很有用的 - 具体请查看 HackerNews demo 的参考设置

扩展说明(Externals Caveats)

请注意,在externals选项中,我们将 CSS 文件列入白名单。这是因为从依赖模块导入的 CSS 还应该由 webpack 处理。如果你导入依赖于 webpack 的任何其他类型的文件(例如*.vue,*.sass),那么你也应该将它们添加到白名单中。

如果你使用runInNewContext: 'once'runInNewContext: true,那么你还应该将修改global的 polyfill 列入白名单,例如babel-polyfill。这是因为当使用新的上下文模式时,server bundle 中的代码具有自己的global对象。由于在使用 Node 7.6+ 时,在服务器并不真正需要它,所以实际上只需在客户端 entry 导入它。

客户端配置(Client Config)

客户端配置(client config)和基本配置(base config)大体上相同。显然你需要把entry指向你的客户端入口文件。除此之外,如果你使用CommonsChunkPlugin,请确保仅在客户端配置(client config)中使用,因为服务器包需要单独的入口 chunk。

生成clientManifest

需要版本 2.3.0+

除了 server bundle 之外,我们还可以生成客户端构建清单(client build manifest)。使用客户端清单(client manifest)和服务器 bundle(server bundle),renderer 现在具有了服务器和客户端的构建信息,因此它可以自动推断和注入资源预加载 / 数据预取指令(preload / prefetch directive),以及 css 链接 / script 标签到所渲染的 HTML。

好处是双重的:

  1. 在生成的文件名中有哈希时,可以取代html-webpack-plugin来注入正确的资源 URL。
  2. 在通过 webpack 的按需代码分割特性渲染 bundle 时,我们可以确保对 chunk 进行最优化的资源预加载/数据预取,并且还可以将所需的异步 chunk 智能地注入为<script>标签,以避免客户端的瀑布式请求(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 运行时分离到一个引导 chunk 中,
    // 以便可以在之后正确注入异步 chunk。
    // 这也为你的 应用程序/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
})

通过以上设置,使用代码分割特性构建后的服务器渲染的 HTML 代码,将看起来如下(所有都是自动注入):

<html>
  <head>
    <!-- 用于当前渲染的 chunk 会被资源预加载(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 会被数据预取(preload)(次要优先级) -->
    <link rel="prefetch" href="/1.js" as="script">
  </head>
  <body>
    <!-- 应用程序内容 -->
    <div data-server-rendered="true"><div>async</div></div>
    <!-- manifest chunk 优先 -->
    <script src="/manifest.js"></script>
    <!-- 在主 chunk 之前注入异步 chunk -->
    <script src="/0.js"></script>
    <script src="/main.js"></script>
  </body>
</html>`

手动资源注入(Manual Asset Injection)

默认情况下,当提供template渲染选项时,资源注入是自动执行的。但是有时候,你可能需要对资源注入的模板进行更细粒度(finer-grained)的控制,或者你根本不使用模板。在这种情况下,你可以在创建 renderer 并手动执行资源注入时,传入inject: false

renderToString回调函数中,你传入的context对象会暴露以下方法:

  • context.renderStyles()

这将返回内联<style>标签包含所有关键 CSS(critical CSS) ,其中关键 CSS 是在要用到的*.vue组件的渲染过程中收集的。有关更多详细信息,请查看CSS 管理

如果提供了clientManifest,返回的字符串中,也将包含着<link rel="stylesheet">标签内由 webpack 输出(webpack-emitted)的 CSS 文件(例如,使用extract-text-webpack-plugin提取的 CSS,或使用file-loader导入的 CSS)

  • context.renderState(options?: Object)

    此方法序列化context.state并返回一个内联的 script,其中状态被嵌入在window.__INITIAL_STATE__中。

    上下文状态键(context state key)和 window 状态键(window state key),都可以通过传递选项对象进行自定义:

  context.renderState({
    contextKey: 'myCustomState',
    windowKey: '__MY_STATE__'
  })
  // -> <script>window.__MY_STATE__={...}</script>
  • context.renderScripts()

    • 需要clientManifest

    此方法返回引导客户端应用程序所需的<script>标签。当在应用程序代码中使用异步代码分割(async code-splitting)时,此方法将智能地正确的推断需要引入的那些异步 chunk。

  • context.renderResourceHints()

    • 需要clientManifest

    此方法返回当前要渲染的页面,所需的<link rel="preload/prefetch">资源提示(resource hint)。默认情况下会:

  • 预加载页面所需的 JavaScript 和 CSS 文件

  • 预取异步 JavaScript chunk,之后可能会用于渲染

    使用shouldPreload选项可以进一步自定义要预加载的文件。

  • context.getPreloadFiles()

    • 需要clientManifest

    此方法不返回字符串 - 相反,它返回一个数组,此数组是由要预加载的资源文件对象所组成。这可以用在以编程方式(programmatically)执行 HTTP/2 服务器推送(HTTP/2 server push)。

由于传递给createBundleRenderertemplate将会使用context对象进行插值,你可以(通过传入inject: false)在模板中使用这些方法:

<html>
  <head>
    <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
    {{{ renderResourceHints() }}}
    {{{ renderStyles() }}}
  </head>
  <body>
    <!--vue-ssr-outlet-->
    {{{ renderState() }}}
    {{{ renderScripts() }}}
  </body>
</html>

如果你根本没有使用template,你可以自己拼接字符串。

如果本教程对您帮助很大,请随意打赏。您的支持,将鼓励我们提供更好的教程!

← 键盘方向键翻页 →
返回顶部 手机访问 关注微信 返回底部

扫码访问歪脖网

随时随地,想看就看

关注歪脖网微信

分享 web 知识、交流 web 经验