Express-vue2.0-server-render初探

#Vue2.0服务端渲染

快速的过一遍Node搭建服务之后,今天我们终于可以看下如何进行vue服务端渲染啦。

为什么要SSR?

为了更快

我们看下两种方式的区别
客户端渲染: 请求html->请求js->请求数据->渲染数据->html
服务端渲染: 请求html->服务端请求数据(内网)->服务端渲染->html

对于客户端来说,经常会存在带宽差、网络慢的情况,因此我们可以尽量减少外网请求,通过更快的路程将首页呈现给用户。

老版本引擎

对于一些不支持vue的老机子上,我们想将内容简单呈现时,可以在服务端进行渲染来提供基本内容。

SEO搜索引擎优化

客户端往往进入页面只有loading动画,通过ajax获取数据,爬虫不会等待加载完成,不利于SEO,而服务端则将整个页面进行输出,可以进行SEO。

其他?

缓存,通过设置缓存可以设置组件渲染时若可以找到缓存,直接返回缓存内容,进行更快的渲染。

如何进行SSR?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//1.创建实例
var app = new Vue({...});
//2.创建渲染器
var renderer = require('vue-server-renderer').createRenderer();
//3.渲染html
renderer.renderToString(app, function(err, html){})
renderer.renderToStream(app)
//html拼接
function(err,html){
html+=html;
res.senm(html);
};
//配合express
var layout = fs.readFileSync('./index.html', 'utf8')
response.send(layout.replace('<div id="app"></div>', html))

流式响应

支持流式渲染,允许HTML一边生成一遍写入响应流,请求服务速度更快~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//写入html头部
res.write(HTML.head)
//响应内容
var stream = renderer.renderToStream(require('./assets/app')());
// 每当新的块被渲染
stream.on('data', function (chunk) {
//首屏的动态数据通过window.__INITIAL_STATE__发送
res.write(`<script>window.__INITIAL_STATE__ = state;</script>`)
// 将块写入响应
res.write(chunk)
})
srream.on('end', function(){
//写入html尾部
res.write(HTML.tail);
})

设置组件缓存

  • 通过组件缓存进一步提高性能
  • 通过lru-cache进行缓存

例如这样:

1
2
3
4
5
6
var render = createRender({
cache: require('lru-cache')({
max: 1000,
maxAge: 1000 * 60 * 15
})
})

对于想要进行缓存的组件,需要多提供两个参数

  • name, 组件名字
  • serverCacheKey函数,返回唯一组件作用域

缓存适用于以下场景

  • 静态的组件 (例如 总是尝试一样的HTML,所以 serverCacheKey 函数可以被返回 true)
  • 列表组件 (通过缓存改善性能)
  • 通用UI组件,通过Props进行数据传递

注意

不应该缓存组件包含子组件依赖全局状态(例如来自vuex的状态)。如果这么做,子组件(事实上是整个子树)也会被缓存。所以要特别注意带有slots片段或者子组件的情况。

数据渲染

我们最关心的是,如何去加载依赖数据呢?

BundleRenderer

server Bundle

1.使用webpack + vue-loader进行打包,生成server bundle

1
2
3
4
5
6
7
8
9
module.exports = {
//添加Node target
target: 'node',
//output添加LibraryTarget参数
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
}
}

2.bundle暴露函数,返回包含app根实例的Promise。

1
2
3
4
5
6
7
8
const app = new Vue(App)
export default context => {
// data pre-fetching
return app.fetchServerData(context.url).then(() => {
return app
})
}

整合router,vuex

在组件上提供preFetch函数,服务端渲染时会主动差早挂载的部分,调用时进行数据抓取。(需要router进行整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default context => {
router.push(context.url)
//进行请求合并
return Promise.all(router.getMatchedComponents().map(component => {
if (component.preFetch) {
return component.preFetch(store)
}
})).then(() => {
// After all preFetch hooks are resolved, our store is now
// filled with the state needed to render the app.
// Expose the state on the render context, and let the request handler
// inline the state in the HTML response. This allows the client-side
// store to pick-up the server-side state without having to duplicate
// the initial data fetching on the client.
context.initialState = store.state
return app
})
}

最后,可以参考express-server-side库来搭建express服务端渲染vue-ssr,参考vue-ssr-hmr-template来对vuex,router等进行整合。包括

  • Vue 2.0
  • Webpack 2.1.0
  • HotModuleReplacement
  • Server Side Render
  • Express

最后上官方的例子

vue-hackernews-2.0