blog
blog copied to clipboard
vue-server-renderer插件在ssr中做了什么?
vue-server-renderer插件
vue-server-renderer
是用来做Vue项目的服务端渲染,Vue的服务端渲染框架Nuxt也是基于vue-server-renderer
,下面我们来看看这个插件到底做了什么事情。
运行项目
运行demo,同时结合vue-server-renderer源码,带着下面的问题去研究一下vue-server-renderer
。
- VueSSRClientPlugin做了什么事情?
- VueSSRServerPlugin做了什么事情?
- 服务端渲染获取的数据,如何同步到客户端渲染的页面?
VueSSRClientPlugin源码
VueSSRClientPlugin的作用是生成客户端构建清单,清单的名字默认为vue-ssr-client-manifest.json。这个json对象包含webpack整个构建过程的信息,从而能bundle renderer自动推导出在html需要注入的内容,自动推断出最佳的预加载(preload)和预取(prefetch)指令,以及初始渲染所需的代码分割 chunk。。
var VueSSRClientPlugin = function VueSSRClientPlugin (options) {
if ( options === void 0 ) options = {};
// 默认输出名称为vue-ssr-client-manifest.json
this.options = Object.assign({
filename: 'vue-ssr-client-manifest.json'
}, options);
};
VueSSRClientPlugin.prototype.apply = function apply (compiler) {
var this$1 = this;
// 监听emit事件,emit事件在生成资源到 output 目录之前触发。
compiler.plugin('emit', function (compilation, cb) {
var stats = compilation.getStats().toJson();
// 所有的文件
var allFiles = uniq(stats.assets
.map(function (a) { return a.name; }));
// 初始化的文件数组
var initialFiles = uniq(Object.keys(stats.entrypoints)
.map(function (name) { return stats.entrypoints[name].assets; })
.reduce(function (assets, all) { return all.concat(assets); }, [])
.filter(isJS));
// 异步文件数组
var asyncFiles = allFiles
.filter(isJS)
.filter(function (file) { return initialFiles.indexOf(file) < 0; });
// 最后输出的json对象
var manifest = {
publicPath: stats.publicPath,
all: allFiles,
initial: initialFiles,
async: asyncFiles,
modules: { /* [identifier: string]: Array<index: number> */ }
};
// 设置manifest.modules的值,表示模块属于哪个chunk文件
var assetModules = stats.modules.filter(function (m) { return m.assets.length; });
var fileToIndex = function (file) { return manifest.all.indexOf(file); };
stats.modules.forEach(function (m) {
// ignore modules duplicated in multiple chunks
if (m.chunks.length === 1) {
var cid = m.chunks[0];
var chunk = stats.chunks.find(function (c) { return c.id === cid; });
if (!chunk || !chunk.files) {
return
}
var files = manifest.modules[hash(m.identifier)] = chunk.files.map(fileToIndex);
// find all asset modules associated with the same chunk
assetModules.forEach(function (m) {
if (m.chunks.some(function (id) { return id === cid; })) {
files.push.apply(files, m.assets.map(fileToIndex));
}
});
}
});
// 输出json对象
var json = JSON.stringify(manifest, null, 2);
compilation.assets[this$1.options.filename] = {
source: function () { return json; },
size: function () { return json.length; }
};
cb();
});
};
打包生成的vue-ssr-client-manifest.json
文件
{
"publicPath": "",
// 所有的资源文件
"all": [
"0.js",
"1.js",
"2.js",
"clientBundle.js" // webpack.client.conf.js里面entry的key
],
// 初始化需要的文件
"initial": [
"clientBundle.js"
],
// 异步的文件
"async": [
"0.js",
"1.js",
"2.js"
],
// 模块
"modules": {
// entry-client.js模块属于all[3], 即clientBundle.js
"2ce556e0": [
3
],
// App.vue模块属于all[3], 即clientBundle.js
"96f2ad42": [
3
],
//...
}
}
VueSSRServerPlugin源码
生成server buddle文件,默认为vue-ssr-server-bundle.json,用于获取资源文件的内容,比如0.js、1.js、serverBundle.js的内容。
var VueSSRServerPlugin = function VueSSRServerPlugin (options) {
if ( options === void 0 ) options = {};
// 默认输出名字为vue-ssr-server-bundle.json
this.options = Object.assign({
filename: 'vue-ssr-server-bundle.json'
}, options);
};
VueSSRServerPlugin.prototype.apply = function apply (compiler) {
var this$1 = this;
validate(compiler);
compiler.plugin('emit', function (compilation, cb) {
var stats = compilation.getStats().toJson();
var entryName = Object.keys(stats.entrypoints)[0];
var entryInfo = stats.entrypoints[entryName];
var entryAssets = entryInfo.assets.filter(isJS);
var entry = entryAssets[0];
var bundle = {
entry: entry,
files: {},
maps: {}
};
stats.assets.forEach(function (asset) {
if (asset.name.match(/\.js$/)) {
bundle.files[asset.name] = compilation.assets[asset.name].source();
} else if (asset.name.match(/\.js\.map$/)) {
bundle.maps[asset.name.replace(/\.map$/, '')] = JSON.parse(compilation.assets[asset.name].source());
}
// do not emit anything else for server
delete compilation.assets[asset.name];
});
var json = JSON.stringify(bundle, null, 2);
var filename = this$1.options.filename;
compilation.assets[filename] = {
source: function () { return json; },
size: function () { return json.length; }
};
cb();
});
};
输出的vue-ssr-server-bundle.json:
{
"entry": "serverBundle.js",
"files": {
"0.js": "...",
"1.js": "...",
"2.js": "...",
"serverBundle.js": "...", // webpack.server.conf.js里面entry的key
}
生成客户端清单vue-ssr-client-manifest.json和服务端bundle对象vue-ssr-server-bundle.json,就可以通过createBundleRenderer
渲染把vue文件
const bundle = require(path.resolve(__dirname, 'dist/vue-ssr-server-bundle.json'));
const renderer = require('vue-server-renderer').createBundleRenderer(bundle, {
template: fs.readFileSync(path.resolve(__dirname, 'index.ssr.html'), 'utf-8'),
clientManifest: require(path.resolve(__dirname, 'dist/vue-ssr-client-manifest.json')),
basedir: path.resolve(__dirname, './dist'),
});
vue-server-renderer的作用
const Vue = require('vue')
const app = new Vue({
template: `<div>Hello World</div>`
})
const renderer = require('vue-server-renderer').createRenderer()
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
})
// => <div data-server-rendered="true">Hello World</div>
render的核心代码在vue目录的server文件夹下
服务端渲染获取的数据,如何同步到客户端渲染的页面?
服务端获取数据,保存到服务端的store中。在renderer中会在代码中加入<script>window.__INITIAL_STATE__ = ...</script>
。在客户端初始化store时,判断是否存在window.__INITIAL_STATE__
,如果存在就替换store.state。
TemplateRenderer.prototype.renderState = function renderState (context, options) {
var ref = options || {};
var contextKey = ref.contextKey; if ( contextKey === void 0 ) contextKey = 'state';
var windowKey = ref.windowKey; if ( windowKey === void 0 ) windowKey = '__INITIAL_STATE__';
var autoRemove = process.env.NODE_ENV === 'production'
? '(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());'
: '';
return context[contextKey]
? ("<script>window." + windowKey + "=" + (serialize(context[contextKey], { isJSON: true })) + autoRemove + "</script>")
: ''
};