用于 Vue2.0 的服务端渲染
这个包是自动生成的。如果你想合并请求请看 GigHub 上的 src/entries/web-server-renderer.js
这个包提供了基于 Node.js 的 Vue2.0 服务端渲染。
npm install vue-server-rederer
创建一个 randerer
实例。
const renderer = require('vue-server-renderer').createRenderer();
渲染一个 string
类型的 Vue 实例。它的回调函数是第一个参数为接受到的 error
对象的标准 Node.js 的回调函数形式。
const Vue = require('vue') const renderer = require('vue-server-renderer').createRenderer() const vm = new Vue({ render (h) { return h('div', 'hello') } }) renderer.renderToString(vm, (err, html) => { console.log(html) // -> <div server-rendered="true">hello</div> })
渲染一个流模式的 Vue 实例。返回一个 Node.js 的可读流。
// 使用 express 的一个例子 app.get('/', (req, res) => { const vm = new App({ url: req.url }) const stream = renderer.renderToStream(vm) res.write(`<!DOCTYPE html><html><head><title>...</title></head><body>`) stream.on('data', chunk => { res.write(chunk) }) stream.on('end', () => { res.end('</body></html>') }) })
通过预打包应用程序代码(请看)来创建一个 bundleRenderer
实例。对于每一次渲染调用,使用 Node.js 的 vm 模块实现代码在新的上下文中重新执行。这确保了你的应用程序状态是在请求之间是相互隔离的,并且你不用担心为了服务端渲染而在一个限制模式下构建你的应用。
const bundleRenderer = require('vue-server-renderer').createBundleRenderer(code)
渲染一个 string
类型的打包好的应用。和 renderer.renderToString
相同的回调接口。这个上下文参数对象将会传递给打包的输出函数。
bundleRenderer.renderToString({ url: '/' }, (err, html) => { // ... })
渲染一个流模式的打包好的应用。和 renderer.renderToStream
想用的流式接口。这个上下文对象将会传递给打包的输出函数。
bundleRenderer .renderToStream({ url: '/' }) .pipe(writableStream)
允许你提供一些自定义指令用于服务端渲染的实现。
const renderer = createRenderer({ directives: { example (vnode, directiveMeta) { // transform vnode based on directive binding metadata // 基于绑定元数据的指令转化 vnode } } })
还有一个例子, v-show’s server-side implementation 。
提供了一种的实现。这个缓存对象必须实现下面的接口:
{ get: (key: string, [cb: Function]) => string | void, set: (key: string, val: string) => void, has?: (key: string, [cb: Function]) => boolean | void // optional }
一个典型的应用是传一个 lrc-cache :
const LRU = require('lru-cache') const renderer = createRenderer({ cache: LRU({ max: 10000 }) })
注意,缓存对象至少应该设置 get
和 set
。另外, get
方法如果提供了第二个参数作为回调函数那么还可以选择异步使用。这允许缓存使用异步APIs,例如redis client 例子:
const renderer = createRenderer({ cache: { get: (key, cb) => { redisClient.get(key, (err, res) => { // handle error if any cb(res) }) }, set: (key, val) => { redisClient.set(key, val) } } })
在一个典型的 Node.js 应用中,后端服务是一个长时间运行的进程。如果我们直接请求我们的应用代码,实例化模块将会在每个请求中共享。这带了一些不方便的限制在我们构建时:比如我们必须避免使用全局状态单例(例如:Vuex里面的store),否则每一次状态改变都会导致下一次请求被影响。
相反, bundleRenderer
更容易保证我们的每一个请求在运行的应用程序中都是“新的”(即实例化模块不共享),这样我们不需要去考虑通过每次请求避免状态污染的问题了。这些都是 bundleRenderer
帮助我们实现的。
应用程序的打包可以通过任何构建工具生成,你可以使用很简单的 Webpack + vue-loader 还有 bundleRenderer实现。你需要使用略微不同的 webpack 配置和为了服务端渲染的入口文件,但是这个不同是很小的:
为你的 webpack 配置添加 target:'node'
和 output:{libraryTarget:'commonjs2'}
。这可能是一个比较好的处理你的外部依赖文件的方法。
在你的服务端入口文件抛出一个方法。这个方法将会接收到渲染的上下文对象(传递给 bundleRenderer.renderToString
或者 bundleRenderer.renderToStream
),并且返回一个Promise,这将最终解决应用程序 Vue 根实例的问题。
// server-entry.js import Vue from 'vue' import App from './App.vue' const app = new Vue(App) // 默认的输出应该是一个接收渲染调用时上文对象的函数。 export default context => { // data pre-fetching return app.fetchServerData(context.url).then(() => { return app }) }
我们在使用 bundleRenderer
时,最好在服务端打包时通过默认的打包方式把每一个依赖文件打进我们的应用程序。这意味着在每次请求那些依赖文件时将需要解析和运行一遍,但是在大多数情况下这些是不需要的。
我们能够通过你打的包来优化这些外部依赖关系。在渲染过程中,任何原始的 require()
调用都将返回实际的 Node 模块从你的渲染进程中。使用 webpack ,我们可以很简单的通过 externals
配置选项来列举出那些我们想要处理的外部依赖模块。
// webpack.config.js module.exports = { // 这些外部依赖中的所有模块都在你的package.json文件中的 “dependencies” 下面 externals: Object.keys(require('./package.json').dependencies) }
由于外部依赖模块在每个请求中是共享的,所以你必须保证这些依赖关系是等同的。这样,通过不同的请求应该总是返回相同的结果,并且它不能拥有通过你的应用程序来改变的全局状态(如:使用 Vue 插件)。
你可以容易的通过 serverCacheKyy
函数来在服务端渲染中来缓存组件:
export default { name: 'item', // required props: ['item'], serverCacheKey: props => props.item.id, render (h) { return h('div', this.item.id) } }
注意缓存组件 必须配置唯一的“name”选项 。这对于 Vue 在使用打包渲染时确定组件的身份来说是必须的。
使用唯一的名字来作为缓存组件的的键名:你不用担心两个组件返回相同的键。一个缓存组件的键名应该包含足够的信息来表述它渲染的结果。上面的这种方式是一种很好的实现如果这个渲染结果可以通过 props.item.id
能完全确定。然而,这个组件随着时间的推移原本的 ID 可能会改变,或者渲染结果还依赖另一个 prop
,这样你需要去修改你的 getCacheKey
来实现获取其他的变量在程序中。
返回一个常数总会被缓存,这对于纯粹的静态组件来说是很好的。
如果渲染器在渲染期间渲染了一个组件,它将直接为真个子树重用缓存结果。所以 不要缓存包含全局状态的子组件 。
在大多数情况下,你不应该和不需要缓存简单的实例组件。最常见的组件缓存需要大名单。由于这些组件通常是一些由数据库中的对象集合驱动的,他们可以使用一些简单的缓存策略。生成它们的缓存键名使用它们自己的唯一ID加最后更新的时间戳。
serverCacheKey: props => props.item.id + '::' + props.item.last_updated
在服务端渲染输出,根元素将会有一个 server-rendered=true
的属性标记。在客户端,当你使用这个属性挂载一个 Vue 实例到元素上时,它将尝试合成到现有的DOM实例而不是创建新的DOM节点。
在开发模型中,Vue 将维护客户端生成的虚拟DOM树来匹配来自服务端渲染的DOM结构。如果不匹配,它将放弃合成,维持现有DOM并且从头开始渲染。 在生产模型下,这种维护是被禁用的为了更高的性能。
有一些事情需要特别注意,当使用服务端渲染+客户端合成一些特殊HTML结构时,浏览器可能会改变HTML结构。比如,当你写下面这个样的Vue实例的时候:
<table> <tr><td>hello word</td></tr> </table>
浏览器会自动添加 tbody
到 table
中,然而,Vue生成的虚拟DOM不会包含 tbody
,所以将会导致不匹配。为了确保正确的匹配,请准确书写有效的HTML在你的模板中。
本文为个人在学习 Vue 服务端渲染时,翻译自 npm:vue-server-renderer 的README文档,本人初学 Vue 能力有限,翻译有误地方请大家指出。