vue-server-renderer 文档翻译

vue-server-renderer

用于 Vue2.0 的服务端渲染

这个包是自动生成的。如果你想合并请求请看 GigHub 上的 src/entries/web-server-renderer.js

这个包提供了基于 Node.js 的 Vue2.0 服务端渲染。

<a name="installation" href="#installation">安装</a>

npm install vue-server-rederer

<a name="api" >API</a>

createRenderer([rendererOptions])

创建一个randerer实例。

const renderer = require('vue-server-renderer').createRenderer();

renderer.renderToString(vm,cb)

渲染一个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> 
})

renderer.renderToStream(vm)

渲染一个流模式的 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>')
  })
})

createBundleRenderer(code,[rendererOptions])

通过预打包应用程序代码(请看服务端打包生成)来创建一个bundleRenderer实例。对于每一次渲染调用,使用 Node.js 的 vm 模块实现代码在新的上下文中重新执行。这确保了你的应用程序状态是在请求之间是相互隔离的,并且你不用担心为了服务端渲染而在一个限制模式下构建你的应用。

const bundleRenderer = require('vue-server-renderer').createBundleRenderer(code)

bundleRenderer.renderToString([context],cb)

渲染一个string类型的打包好的应用。和renderer.renderToString相同的回调接口。这个上下文参数对象将会传递给打包的输出函数。

bundleRenderer.renderToString({ url: '/' }, (err, html) => {
  // ... 
})

bundleRenderer.renderToString([context])

渲染一个流模式的打包好的应用。和renderer.renderToStream想用的流式接口。这个上下文对象将会传递给打包的输出函数。

bundleRenderer
  .renderToStream({ url: '/' })
  .pipe(writableStream)

<a name="renderer-options">Renderer 选项</a>

指令

允许你提供一些自定义指令用于服务端渲染的实现。

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

注意,缓存对象至少应该设置getset。另外,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)
    }
  }
})

<a name="why-use-bundlerrenderer">为什么使用 bundleRenderer?</a>

在一个典型的 Node.js 应用中,后端服务是一个长时间运行的进程。如果我们直接请求我们的应用代码,实例化模块将会在每个请求中共享。这带了一些不方便的限制在我们构建时:比如我们必须避免使用全局状态单例(例如:Vuex里面的store),否则每一次状态改变都会导致下一次请求被影响。

相反,bundleRenderer更容易保证我们的每一个请求在运行的应用程序中都是“新的”(即实例化模块不共享),这样我们不需要去考虑通过每次请求避免状态污染的问题了。这些都是bundleRenderer帮助我们实现的。

<a name="creating-the-server-bundle">服务端打包生成</a>

服务端打包生成

应用程序的打包可以通过任何构建工具生成,你可以使用很简单的 Webpack + vue-loader 还有 bundleRenderer实现。你需要使用略微不同的 webpack 配置和为了服务端渲染的入口文件,但是这个不同是很小的:

  1. 为你的 webpack 配置添加 target:'node'output:{libraryTarget:'commonjs2'} 。这可能是一个比较好的处理你的外部依赖文件的方法。

  2. 在你的服务端入口文件抛出一个方法。这个方法将会接收到渲染的上下文对象(传递给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 插件)。

<a name="component-caching">组件缓存</a>

你可以容易的通过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

<a name="client-side-hydration">客户端合成</a>

在服务端渲染输出,根元素将会有一个server-rendered=true的属性标记。在客户端,当你使用这个属性挂载一个 Vue 实例到元素上时,它将尝试合成到现有的DOM实例而不是创建新的DOM节点。

在开发模型中,Vue 将维护客户端生成的虚拟DOM树来匹配来自服务端渲染的DOM结构。如果不匹配,它将放弃合成,维持现有DOM并且从头开始渲染。在生产模型下,这种维护是被禁用的为了更高的性能。

合成警告

有一些事情需要特别注意,当使用服务端渲染+客户端合成一些特殊HTML结构时,浏览器可能会改变HTML结构。比如,当你写下面这个样的Vue实例的时候:

<table>
    <tr><td>hello word</td></tr>
</table>

浏览器会自动添加tbodytable中,然而,Vue生成的虚拟DOM不会包含tbody,所以将会导致不匹配。为了确保正确的匹配,请准确书写有效的HTML在你的模板中。

说明

本文为个人在学习 Vue 服务端渲染时,翻译自npm:vue-server-renderer的README文档,本人初学 Vue 能力有限,翻译有误地方请大家指出。

另外,如果想学习Vue官方的服务端渲染的例子vue-ssr-demo-simplevue-hackernews-2.0的例子,而又对各种webpack配置和英文注释有些迷茫,可以看一下我对官方vue-ssr-demo-simplevue-vue-hackernews-2.0一个注释版本,根据自己的理解加的注释。当然个人能力有限,可能有些地方理解有误,还请谅解

  1. vue-ssr-demo-simple 个人理解版本

  2. vue-vue-hackernews-2.0 个人理解版本

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 转载 :OpenDiggawesome-github-vue 是由OpenDigg整理并维护的Vue相关开源项目库...
    果汁密码阅读 22,987评论 8 125
  • 去美丽的丽江古城来个难忘的艳遇 去风景优美的鼓浪屿感受一下海风轻拂你的脸颊 去古老悠久的罗马体会一下浓厚的浪漫气息...
    小杜娟呐阅读 169评论 6 1
  • 浑浑噩噩。一个人呆在图书馆。闷的感觉快要窒息。下楼,看到他们,最终还是转身走了。毕竟,已形同陌路。再去又有何意义呢...
    7c5031823170阅读 247评论 0 1
  • 阿蛮的父母和小萝的父母是很好的朋友。阿蛮的父母和小萝父母大学是好友,毕业后小萝的爸爸和阿蛮的爸爸没有去找工作而是开...
    憶憶憶諾星xing阅读 392评论 0 1