ssr服务器端渲染
1 SSR
全称:server side render(服务器端渲染),让我们可以在服务器端渲染应用程序
前端渲染问题:
1 白屏时间长,影响用户体验
2 不利于搜索引擎优化(SEO)
所以我们要在服务器端渲染应用程序
服务器端渲染:vue为服务器端渲染提供了模板–vue-server-render
该模块提供了createRenderer方法,来创建渲染器
渲染器提供了renderToString方法,渲染应用程序组件
渲染器实现了promise规范,因此可以通过then方法监听成功,可以通过cache方法监听失败
渲染模板
渲染模板:想在模板中渲染应用程序分成两步:
第一步 在createRenderer方法中,通过template引入模板文件
第二步 在模板文件中,通过 定义应用程序渲染位置
前后端同步渲染
浏览器端渲染(前端):
1 白屏时间长,影响用户体验 2 不利于SEO优化
服务器端渲染(后端):
无法绑定交互
为了解决浏览器端与服务器端渲染的问题,我们要使用前后端同步渲染技术
- 1 html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <!-- 定义渲染位置 --> <!-- 注意,不要添加空格 --> <!--vue-ssr-outlet--></body></html>
- 2 组件文件test.js
// 引入vuelet Vue = require('vue');// 实例化module.exports = new Vue({ template: ` <div> <h1 @click="clickH1">hello {{msg}}</h1> </div> `, data: { msg: 'test' }, methods: { // 服务器端渲染无法绑定交互,所以此点击事件不会生效 clickH1() { console.log('click h1'); } },})
- 3 服务器端
// 引入expresslet express = require('express');// 引入ejslet ejs = require('ejs');// 引入vue在服务器端的渲染器let { createRenderer } = require('vue-server-renderer');// 引入fslet fs = require('fs');// 引入pathlet path = require('path');// 引入组件let testApp = require('./test');// 获取根目录let root = process.cwd();// 创建渲染器let render = createRenderer({ // 同步读取,引入模板文件 template: fs.readFileSync(path.join(root, './views/index.html'), 'utf-8')})// 定义应用程序let app = express()// 拓展名app.engine('.html', ejs.__express)// 定义路由app.get('/', (req, res) => { // 渲染字符串 render.renderToString(testApp) .then( // 渲染成功 data => res.end(data), // 渲染失败 err => console.log('fail', err) )})// 监听端口号app.listen(3000)
此时,就将组件渲染出来了,但是没有交互
2 前后端同步渲染
前端渲染
是为了给用户使用,因此希望资源加载的块(压缩,打包,添加指纹等等)
为了更好的效果,我们要加载样式;为了看到页面,我们要处理模板等,。。。
但是服务器端渲染不需要考虑这些问题,只需要一个应用程序组件,并最终将其渲染成字符串
因此前后端渲染有不同的入口文件,有不同的webpack配置
有不同的入口文件
前端入口文件,要让应用程序组件上树;后端入口文件,只需要一个应用程序组件
不同的webpack配置
前端要考虑样式,模板,性能优化(压缩,打包,添加指纹等)等
后端只要将ES Module规范,编译成CommonJS规范即可
- 目录部署
config 存储配置文件
home 前端开发的项目
views 模板目录
static 静态资源
app.js 服务器入口文件
- 服务器端配置
1 不需要模板,不用写html插件
2 不需要样式,不用写style-loader,不需要拆分样式
3 不需要性能优化、拆分模块,添加指纹,服务器端本地引入文件,因此打包在一起就可以了
4 通过target:node配置,说明给node使用
5 在output.libraryTarget:commonjs2,将ES Module规范转成commonjs规范
6 使用vue-server-renderer插件,发布json文件
在参数对象中,通过filename属性,可以自定义json文件的发布位置
- 服务器端渲染
为了使用发布的json文件,vue-server-render提供了createBundleRenderer方法
第一个参数表示json文件
第二个参数表示配置对象
template定义模板文件的位置
插值语法:vue服务器端渲染允许我们在渲染页面的时候,向页面中插入一些数据
在renderToString方法中传递数据
在模板中,通过插值语法插入数据
{{}} 渲染文本插值
{{{}}} 渲染标签插值
- 1 html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body>// 渲染位置 <!--vue-ssr-outlet--></body></html>
- 2 app.vue
<template><div id="app"> <h1 @click="clickH1">app part -- {{msg}}-- {{$store.state.num}}</h1></div></template><script>export default { data() { return { msg: 'hello' } }, methods: { clickH1() { console.log('click h1'); } }}</script>
- 入口文件main.js
import Vue from 'vue';// 引入应用程序import App from './App';// 引入storeimport store from './store'// 引入样式import './style.scss';export default new Vue({ // 订阅 store, // 渲染应用程序 render: h => h(App)})
- 浏览器端入口文件entry-client.js
// 引入vue实例化对象,引入main.jsimport app from './main';// 上树app.$mount('#app');
- 服务器端入口文件entry-server.js
import app from './main';服务器端不需要上树// 暴露接口export default app;
- 定义webpack命令
{ "scripts": { "client": "webpack --config ./config/webpack.client.js", "start:client": "webpack -w --config ./config/webpack.client.js", "serve": "webpack --config ./config/webpack.server.js", "start:serve": "webpack -w --config ./config/webpack.server.js", "start": "npm run client && npm run server" }}
- 浏览器端webpack配置
// 引入pathlet path = require('path')// vue插件let { VueLoaderPlugin } = require('vue-loader');// 引入html插件let HtmlWebpackPlugin = require('html-webpack-plugin');// 拆分csslet MiniCssExtractPluin = require('mini-css-extract-plugin')// 压缩csslet OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');// 获取根目录const root = process.cwd();module.exports = { // 模式 mode: 'production', // 解决问题 resolve: { // 别名 alias: { vue$: 'vue/dist/vue.js', '@': path.join(root, './home/src'), '@v': path.join(root, './home/src/views'), '@c': path.join(root, './home/src/components') }, // 扩展名 extensions: ['.js', '.vue'] }, // 入口 entry: './home/src/entry-client.js', // 发布 output: { filename: 'static/[name].js', // 发布的相对位置位置 path: root, // 修改进入静态文件的相对位置 publicPath: '/' }, // 模块 module: { // 加载机 rules: [ // vue { test: /\.vue$/, loader: 'vue-loader' }, // css { test: /\.css$/, use: [ // 'style-loader', // 1 在样式文件的style-loader后面添加loader MiniCssExtractPluin.loader, 'css-loader' ] }, // less { test: /\.less$/, use: [ // 'style-loader', MiniCssExtractPluin.loader, 'css-loader', 'less-loader' ] }, // scss { test: /\.scss$/, use: [ // 'style-loader', MiniCssExtractPluin.loader, 'css-loader', 'sass-loader' ] } ] }, // 插件 plugins: [ // 使用vue加载机需要引入插件 new VueLoaderPlugin(), // 处理模板 new HtmlWebpackPlugin({ // 模板位置 template: './home/public/index.html', // 模板文件发布位置 filename: './views/index.html', // 添加指纹 hash: true, // 优化 防止<!--vue-ssr-outlet-->被压缩 minify: { collapseWhitespace: true, // 取消移除注释 removeComments: false, removeRedundantAttributes:true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, useShortDoctype: true } }), // 2 定义文件发布的位置 new MiniCssExtractPluin({ filename: 'static/style.css' }), // 压缩css new OptimizeCssAssetsPlugin() ], // 拆分库文件 optimization: { // 拆分库文件 splitChunks: { cacheGroups: { lib: { name: 'lib', chunks: 'initial', test: /node_modules/ } } } }}
- 服务器端webpack配置
// 引入pathlet path = require('path')// vue插件let { VueLoaderPlugin } = require('vue-loader');// 引入服务器端渲染插件let VueServerRendererPlugin = require('vue-server-renderer/server-plugin');// 获取根目录const root = process.cwd();module.exports = { // 模式 mode: 'development', 通过target:node配置,说明给node使用 // .....告诉webpack给node使用 target: 'node', // 解决问题 resolve: { // 别名 alias: { vue$: 'vue/dist/vue.js', '@': path.join(root, './home/src'), '@v': path.join(root, './home/src/views'), '@c': path.join(root, './home/src/components') }, // 扩展名 extensions: ['.js', '.vue'] }, // 入口 entry: './home/src/entry-server.js', // 发布 output: { filename: 'static/[name].js', // 发布的相对位置位置 path: root, // 修改进入静态文件的相对位置 publicPath: '/',在output.libraryTarget:commonjs2,将ES Module规范转成commonjs规范 // ......编译成commonjs规范 libraryTarget: 'commonjs2' }, // 模块 module: { // 加载机 rules: [ // vue { test: /\.vue$/, loader: 'vue-loader' }, // css { test: /\.css$/, use: [ 'css-loader' ] }, // less { test: /\.less$/, use: [ 'css-loader', 'less-loader' ] }, // scss { test: /\.scss$/, use: [ 'css-loader', 'sass-loader' ] } ] }, // 插件 plugins: [ // 使用vue加载机需要引入插件 new VueLoaderPlugin(), // 发布位置 将ES Module规范,编译成CommonJS规范,输出到bundle.json使用vue-server-renderer插件,发布json文件在参数对象中,通过filename属性,可以自定义json文件的发布位置 new VueServerRendererPlugin({ filename: '/server/bundle.json' }) ]}
- 服务器端app.js
// 引入expresslet express = require('express');// 引入ejslet ejs = require('ejs');let path = require('path');let fs = require('fs');const root = process.cwd();// 引入vuelet Vue = require('vue');// 引入vue在服务器端的渲染器let { createBundleRenderer } = require('vue-server-renderer');// 创建渲染器为了使用发布的json文件,vue-server-render提供了createBundleRenderer方法let renderer = createBundleRenderer(path.join(root, './server/bundle.json'), { // 传递模板 template: fs.readFileSync(path.join(root, './views/index.html'), 'utf-8')})// 定义应用程序let app = express()// 拓展名app.engine('.html', ejs.__express)// 静态化app.use('/static/', express.static(path.join(root, './static')))// 定义路由app.get('/', (req, res) => { 插值语法:vue服务器端渲染允许我们在渲染页面的时候,向页面中插入一些数据在renderToString方法中传递数据 // 渲染 renderer.renderToString() .then( html => res.end(html), err => console.log(err) )})// 监听端口号app.listen(3000)