写这篇文章, Vue 2 还在 Beta 呢...

参考资料

官方文档写得很清楚

似乎 Vue 1 有看到过通过 jsdom 做后端渲染的例子, 性能不佳.
Vue 2 开始将 Virtual DOM 作为底层实现, 于是模块分离开始支持 SSR.

渲染步骤

4 步走战略~

安装 hackernews 的例子, 完整的 app 渲染的例子包括:

  1. 用 Webpack 的 node 模式把整个应用单独打一个包

  2. Node 环境通过 API 将这个包加载到 vm 环境当中

  3. 应用在 vm 内部启动 HTTP 请求抓取当前路由依赖的数据

  4. 生成网页模板, 将 HTML 和初始数据嵌在中间

如果网页依赖的数据少或者不依赖, 可以简化一点,
比如中间抓取 HTTP 的步骤去掉, 可以简化不少,
也许还可以去掉 vm 那步, 直接通过引用文件来生成 HTML.

渲染 API

两套 API 哦... 好像只用带 bundle 那套...

https://github.com/vuejs/vue/...

  • createRenderer([rendererOptions])

  • renderer.renderToString(vm, cb)

  • renderer.renderToStream(vm)

  • createBundleRenderer(code, [rendererOptions])

  • bundleRenderer.renderToString([context], cb)

  • bundleRenderer.renderToStream([context])

后面三个 API 都带上了 bundle, 此外看上去和前面的一样,
bundle 是通过 Node.js 的 vm 模块运行的, 每次的都重新启动一遍代码,
作者解释这样能清空整个 app 的状态,
我推测这是因为用了 Vuex 之后, 数据会被缓存在内部无法清理,
如果是单纯通过 props 传递数据, 应该是可以用前一套 API.

服务端渲染原理

有了 Virtual DOM 就好办了

VNode 定义 https://github.com/vuejs/vue/...

HTML 渲染的代码, 通过 write 同时支持到了 Stream 输出:
https://github.com/vuejs/vue/...
https://github.com/vuejs/vue/...

如果用 bundle 模式, 注意每次都会运行 vm.runInNewContext 新建环境.
https://github.com/vuejs/vue/...
https://github.com/vuejs/vue/...

最后返回用户的 HTML 其实是拼接出来的,
注意首屏的动态数据, 也通过 window.__INITIAL_STATE__ 发送到浏览器,
https://github.com/vuejs/vue-...

缓存

速度快是因为缓存呢吧...

文档 https://github.com/vuejs/vue/...

大致就是如果组件可以根据一个 key 来确定, 就可以进行缓存,
静态的组件当然是有固定的 key, 动态的组件根据 id 等数据生成 key,

serverCacheKey: props => props.item.id + '::' + props.item.last_updated

如果组件可以找到缓存, 就直接返回缓存内容:
https://github.com/vuejs/vue/...

这也就意味着顶层的组件总之就是不能缓存的, 性能开销免不了.
hackernews 的例子本地用 ab 压了一下, Mac Pro 到 130+qps 了,

Concurrency Level:      100
Time taken for tests:   3.013 seconds
Complete requests:      400
Failed requests:        0
Total transferred:      11545200 bytes
HTML transferred:       11506000 bytes
Requests per second:    132.77 [#/sec] (mean)
Time per request:       753.205 [ms] (mean)
Time per request:       7.532 [ms] (mean, across all concurrent requests)
Transfer rate:          3742.21 [Kbytes/sec] received

但是这个 Demo 是用了缓存的, 破坏掉缓存性能落差很大,
我自己做的 Demo, 实际上加上缓存性能还不到这个一半...
看来跟应用的类型是有关的, 特别是节点偏多的应用影响更大.

数据策略

想象一下后端有个浏览器...

对于依赖数据, 目前的方案是在组件定义上提供 preFetch 函数,
服务端渲染时会主动查找挂载的部分, 调用进行数据抓取:
https://github.com/vuejs/vue-...
https://github.com/vuejs/vue-...

官方的例子当中 App 是带了 Vuex 跟 vue-router 的,
所以 preFetch 方案整个集成在这些库当中.
从实验看, 内部嵌套的 preFetch 是不会被调用的, 只能从路由开始,
同时中间要用到 Promise.all 合并请求, 脑补一下.

好吧我觉得这是一个相当简单粗暴的获取数据的办法,
但其实也很难解耦, 不然就要从路由直接推算数据才行,
主要觉得还是不够清晰, 限制挺多, 实际操作能犯错的地方不少.

性能影响

反正比不上模板引擎

编译后大致还能看到 Virtual DOM 的影子, 会有一些性能开销,
不过话说回来 Virtual DOM 本来就很慢, 能优化一点已经不容易了...

module.exports={render:function(){with(this) {
  return _h('li', {
    staticClass: "news-item"
  }, [_h('span', {
    staticClass: "score"
  }, [_s(item.score)]), " ", _h('span', {
    staticClass: "title"
  }, [(item.url) ? [_h('a', {
    attrs: {
      "href": item.url,
      "target": "_blank"
    }
  }, [_s(item.title)]), " ", _h('span', {
    staticClass: "host"
  }, ["(" + _s(_f("host")(item.url)) + ")"])] : [_h('router-link', {
    attrs: {
      "to": '/item/' + item.id
    }

另外 vm.runInNewContext 有潜在的性能问题,
http://stackoverflow.com/q/98...
不清楚用在生产环境是怎样, 我个人对此没有多少经验..

小结

越来越像 React...

Vue 2 算是把这么多内容整合在一起相当不容易,
不过服务端渲染 React 那么久了, 还是没普及开, 性能是大问题,
相比较而言, Vue 2 增加了 cache 机制, 这可以提高性能,
但是依赖数据时会带来启动 vm 开销, 要是代码量不小在么办?
具体效果还是要等正式发布后, 等有权威的评测...

此外服务端抓取数据的策略需要挖一挖, 找找更漂亮的策略,
我个人希望能更好地解耦, 梳理出更加清晰的依赖,
那样也可以适应更多的场景, 灵活地使用, 而不是限定死了这样用.
当然也是因为服务端渲染, 这个本来存在的问题显得更明确了.

如果觉得我的文章对你有用,请随意赞赏

21 条评论
东泽 · 3月25日

题老师讲的真心不错 我贡献一热血demo https://github.com/beauty-enj...

+1 回复

0

赞. 不过配图有点污.

题叶 作者 · 3月25日
0

吓得我立马关了

俳句之神 · 6月19日
0

能不能换个api源这个上班真的不能直视啊

常江涛 · 2 天前
wmui · 7月16日

我也贡献一个热血demo,此文对我写成这个项目帮助很大。https://segmentfault.com/a/11...

+1 回复

极乐君 · 2016年09月06日

很实用的文章,能转到我的网站吗?——http://www.dreawer.com

回复

题叶 作者 · 2016年09月06日

带上原文链接, 然后随意.

回复

萌小屋 · 2016年09月28日

我有个问题想请教一下,服务端,怎么请求数据然后给vue渲染?

回复

题叶 作者 · 2016年09月29日

靠谱的办法是给服务端专门写一套抓取数据的方案, 从路由判断出需要的数据, 然后全抓取下来. 实际处理比较难.

回复

dayj · 2016年10月08日

楼主你好,很好的文章

关于性能问题我有些疑惑,为什么是性能问题导致不能普及,Vue SSR + node server 我觉得非常轻量级,如果能保证应用是无状态的 (stateless & session-less),部署多个负责渲染的服务,或者说扩展起来也非常方便,到底哪里有硬伤。。。目前我们项目准备上 Vue SSR,因为 Java 的服务端渲染实在不好用,不便于前后端分离、协作,不过我们是小厂,性能要求不高

回复

题叶 作者 · 2016年10月08日

写过模板引擎就会知道这中间有着不可跨域的差距. 模板引擎的实现原理是字符串拼接, 在编译阶段就完成大部分拼接, 而数据只是填充进去的. 但是到了 Virtual DOM 的环境当中, 所有的 DOM 都是生成的, 很难实现充分的拼接算法. 虽然有可能分析出静态的组件做预渲染, 但成本略高, 效果也不是非常好. 到了 Vue 这边, 加上用了 vm 组件去隔离代码, 而不是不可变数据, 实际上成本还要升高. 所以整体性能可定是不理想的.

回复

dayj · 2016年10月10日

Java 的 Thymeleaf 模板引擎现在挺流行的 (在 Java 领域),其实 Thymeleaf 就是基于 DOM 渲染的,它的指令与 vuejs 相似是写在 HTML 元素上的,渲染效率与其它模板引擎 PK 确实 P 不过,而且吃内存,但不防碍使用,我认为模板引擎的轮子太多了,基本都把 高效率、高性能 当作首要或仅次于首要的目标,我的意思是我们真需要这么高效率的模板引擎吗?另外,像 Vue SSR 这种渲染,如果性能出现问题,我们可以无痛地横向扩展,我始终认为,确实有点效率问题,但不至于是硬伤。。。

回复

dayj · 2016年10月10日

一两年前当我刚发现 Thymeleaf 的时候,我就知道这才是模板引擎中的创新,那些拼性能拼效率拼跑分的大部分模板引擎,讲真,开发过程依然蛋疼,当然现在前端发展势头太猛,Thymeleaf 很多优势已经不存在了,但 React / Vue SSR 这类服务端渲染技术粗现了,在开发体验、协作便利性上带来太多提升,我认为这方面的优势完全能够弥补效率稍低所带来的缺点

回复

dayj · 2016年10月10日

这是我的个人见解,有不对之处还望指教 ..

回复

题叶 作者 · 2016年10月10日

嗯, 前端最近变化确实蛮快的.

回复

半年的半年 · 2016年12月26日

性能差距不是一点点吧,我自己搭的静态页面,加上缓存qps也勉强达到120+,express+handlebars的静态页面轻松上500+。
本来还打算在项目中应用了,看来还是得慎重考虑下。

回复

题叶 作者 · 2016年12月27日

比不上 handlebars 是肯定的. 不过缓存的情况下应该不会差这么多才对.

回复

小萌 · 2月23日

想问问大伙,后端渲染的需求是不是为了seo?

回复

0

用 Virtual DOM 做 SEO 还是觉得有点慢, 其实在编译期渲染这么说更合适点.

题叶 作者 · 2月23日
小萌 · 2月23日

哦原来是题叶大神啊,我现在有一个方案解决seo不知道是否可信,因为我们是中型网站吧,不是单机那种。
专门为seo搞一个镜像网站这种有什么弊端吗?我是想这样的话,整个用户端就完全而已用vue全家桶进行单页面开发了。避免多页面来带的技术混杂,现在我们是过渡期,ng1 jq freemarker 三合一,开发起来太难受了。

回复

0

没有规模的 SSR 实战经验, 不好给经验, 抱歉.不如社区问, 看看有没有.

题叶 作者 · 6月19日
载入中...
题叶 题叶

15.2k 声望

发布于专栏

题叶

ClojureScript 爱好者.

394 人关注