2019-08-20
Vue
0

目录

服务端渲染,不包含Ajax初始化数据
1. webpack config 创建
2. 独立生成新的应用程序实例,避免交叉请求造成的状态污染
3. ssr.html介绍
4. 创建server服务
服务端渲染,包含Ajax初始化数据
1.服务端vuex覆盖客户端vuex
2.在组件里面申明asyncData函数
3.入口文件执行完asyncData函数后,才返回app记录App当前下所有的子组件,如果存在asyncData函数,则添加到数组。
4.server.js需要添加await
使用serverBundle和clientManifest进行优化
1.两份webpack配置文件分别使用
2.server添加配置
vue-router配置
entry-server.js更改
server.js更改
参考

服务端渲染,不包含Ajax初始化数据

1. webpack config 创建

创建两份webpack配置文件,一份为客户端运行文件,一份服务端运行文件 同时我们也需要创建两个入口文件,一个给客户端,一个给服务端。

webpack.server.conf.js

打包服务端代码

js
target:'node', output:{ libraryTarget:'commonjs2' }

不使用自己生成的server.bundle.js,使用client.bundle.js文件。客户端激活server.bundle.js会在开启node服务时被用到

js
new HtmlWebpackPlugin({ template: path.resolve(__dirname,'../index.ssr.html'), filename:'index.ssr.html', files:{ js:'client.bundle.js' }, excludeChunks:['server'] })

2. 独立生成新的应用程序实例,避免交叉请求造成的状态污染

以函数形式导出,每次服务端调用时执行函数,生成一份全新的app

js
import Vue from 'vue' import App from './App.vue' export default ()=> { const app=new Vue({ el:'#app', render:h=>h(App) }) return { app } }

3. ssr.html介绍

<!--vue-ssr-outlet-->的作用是作为一个占位符,后续通过vue-server-renderer插件,将服务器解析出的组件html字符串插入到这里。

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>服务端渲染</title> </head> <body> <!--vue-ssr-outlet--> <script type="text/javascript" src="<%= htmlWebpackPlugin.options.files.js %>"></script> </body> </html>

4. 创建server服务

js
createBundleRenderer(serverBundle, { /* 选项 */ }) // serverBundle为webpack编译过后的代码 let renderer=createBundleRenderer(bundle,{ // 创建renderer,此时renderer会有两个值,renderToString和renderToStream template:fs.readFileSync(path.resolve(__dirname,'../dist/index.ssr.html'),'utf-8') }) renderer.renderToString((err,html)=>{ // 返回编译过后的代码 console.log(html) ctx.status =200 ctx.body=html })

完整代码:

js
const Koa = require('koa'); const Router = require('koa-router'); const serve = require('koa-static'); const path = require('path'); const fs = require('fs'); const { createBundleRenderer } = require('vue-server-renderer') const backendApp = new Koa(); const backendRouter = new Router(); let bundle=fs.readFileSync(path.resolve(__dirname,'../dist/server.bundle.js'),'utf-8') let renderer=createBundleRenderer(bundle,{ template:fs.readFileSync(path.resolve(__dirname,'../dist/index.ssr.html'),'utf-8') }) backendRouter.get('*',(ctx,next)=>{ renderer.renderToString((err,html)=>{ if (err) { console.error(err); ctx.status = 500; ctx.body = '服务器内部错误'; } else { ctx.body = html; } }) }) backendApp .use(serve(path.resolve(__dirname,'../dist'))) backendApp .use(backendRouter.routes()).use(backendRouter.allowedMethods()) backendApp.listen(3000,()=>{ console.log('服务器端渲染地址: http://localhost:3000'); })

服务端渲染,包含Ajax初始化数据

思路:

  1. 在渲染前,要预先获取所有需要的异步数据,然后存到Vuex的store中。

  2. 在后端渲染时,通过Vuex将获取到的数据注入到相应组件中。

  3. 把store中的数据设置到window.__INITIAL_STATE__属性中。

  4. 在浏览器环境中,通过Vuex将window.__INITIAL_STATE__里面的数据注入到相应组件中。

1.服务端vuex覆盖客户端vuex

js
if (typeof window !== 'undefined' && window.__INITIAL_STATE__) { console.log('window.__INITIAL_STATE__', window.__INITIAL_STATE__); store.replaceState(window.__INITIAL_STATE__); }

2.在组件里面申明asyncData函数

js
fetchInitialData 执行后,最终会返回Promise const fetchInitialData = ({ store }) => { return store.dispatch('fetchBar'); };

3.入口文件执行完asyncData函数后,才返回app记录App当前下所有的子组件,如果存在asyncData函数,则添加到数组。

所有的asyncData执行完毕以后,才会resolve,context.state 会赋值给window.INITIAL_STATE

js
export default (context)=>{ return new Promise((resolve,reject)=>{ const {App,store,app}=createApp() let components=App.components let asyncDataPromiseFns = [] Object.values(components).forEach(component=>{ if(component.asyncData) asyncDataPromiseFns.push(component.asyncData({store})) }) Promise.all(asyncDataPromiseFns).then(result=>{ context.state = store.state; console.log(222); console.log(store.state); console.log(context.state); console.log(context); resolve(app); }) }) }

4.server.js需要添加await

因为现在入口文件返回的是Promise,且在异步返回以后,才会return。 所以我们服务端需要等待入口文件的asyncData执行完以后,才能往下走,否则会有问题

js
try { let html=await new Promise((resolve,reject)=>{ renderer.renderToString((err,html)=>{ if (err) { reject(err) } else { resolve(html) } }) }) ctx.status=200 ctx.body=html } catch (err) { console.error(err); ctx.status = 500; ctx.body = '服务器内部错误'; }

使用serverBundle和clientManifest进行优化

  1. 内置的 source map 支持(在 webpack 配置中使用 devtool: 'source-map') 在开发环境甚至部署过程中热重载(通过读取更新后的 bundle,然后重新创建 renderer 实例)
  2. 关键 CSS(critical CSS) 注入(在使用 *.vue 文件时):自动内联在渲染过程中用到的组件所需的CSS。更多细节请查看 CSS 章节。
  3. 使用 clientManifest 进行资源注入:自动推断出最佳的预加载(preload)和预取(prefetch)指令,以及初始渲染所需的代码分割 chunk。

1.两份webpack配置文件分别使用

js
require('vue-server-renderer/client-plugin') 和 require('vue-server-renderer/server-plugin')

webpack.server.conf.js

js
const nodeExternals = require('webpack-node-externals') devtool: '#source-map', // 对bundle renderer 提供 source map 支持 externals: [nodeExternals()], // 打包时忽略node_modules里面的包

2.server添加配置

js
const serverBundle = require(path.resolve(__dirname, '../dist/vue-ssr-server-bundle.json')); const clientManifest = require(path.resolve(__dirname, '../dist/vue-ssr-client-manifest.json')); const template = fs.readFileSync(path.resolve(__dirname, '../dist/index.ssr.html'), 'utf-8'); let renderer=createBundleRenderer(serverBundle,{ runInNewContext: false, template, clientManifest })

vue-router配置

entry-server.js更改

js
router.push(context.url) // 路由跳转 router.onReady(() => { const matchedComponents = router.getMatchedComponents(); //获取到当前路由的components if (!matchedComponents.length) { return reject({ code: 404 }); } })

server.js更改

js
let context = { url: ctx.url }; const ssrStream = renderer.renderToStream(context); // 生成的是Node流文件,不是html文本 ctx.status = 200; ctx.type = 'html'; ctx.body = ssrStream;

参考

带你五步学会Vue SSR

本文作者:BARM

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!