vue-ssr(上)
vue-ssr解决什么问题?
spa全部靠的是js来渲染,默认首页显示内容是一个空的div标签,不利于SEO搜索引擎搜索
服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
spa应用会有首页白屏时间过长的问题,服务器渲染的好处是将访问好的数据拼接好给前端,首页白屏时间缩短
服务端渲染的缺点?
需要占用服务器的CPU和内存,前端中的一些生命周期函数无法使用
vue-ssr使用
使用的包
vue vue-server-renderer koa koa-router复制代码
示例
template.html: 这里的 <!--vue-ssr-outlet--> 是固定字段
<!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>复制代码
const Koa = require('koa');const Router = require('koa-router');const Vue = require('vue');const VueServerRenderer = require('vue-server-renderer');const app = new Koa();const router = new Router();const path = require('path');const fs = require('fs');const vm = new Vue({data: {name: '张三'},template: '<div>hello {{name}}</div>'})const template = fs.readFileSync(path.resolve(__dirname, 'template.html'), 'utf8'); router.get('/', async (ctx) => { ctx.body = await VueServerRenderer.createRenderer(template).renderToString(vm); })//使用路由app.use(router.routes()); app.listen(3000, () => {console.log('start server success') })复制代码
通过webpack实现编译vue项目
依赖包
npm i webpack webpack-cli webpack-merge -D npm i @babel/core @babel/preset-env babel-loader -D npm i vue-loader vue-style-loader vue-template-compiler -D npm i css-loader html-webpack-plugin concurrently -D 复制代码
项目结构
ssr ├── dist │ ├── client.bundle.js │ ├── client.html │ ├── server.bundle.js │ └── server.html ├── package-lock.json ├── package.json ├── public │ ├── index.html │ └── index.ssr.html ├── server.js ├── src │ ├── App.vue │ ├── client-entry.js │ ├── components │ │ ├── Bar.vue │ │ └── Foo.vue │ ├── main.js │ └── server-entry.js ├── vue-ssr.md ├── webpack.base.js ├── webpack.client.js └── webpack.server.js复制代码
公共webpack配置
const VueLoaderPlugin = require('vue-loader/lib/plugin')module.exports = {mode: 'development',output: '[name].bundle.js',module: {rules: [{test: /\.css$/, use: ['vue-style-loader', {loader: 'css-loader',options: {esModule: false, //注意配合vue-style-loader使用时需要加上这个属性} }] }, {test: /\.js$/, use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env'] } } }, {test: /\.vue$/, use: 'vue-loader'} ] },plugins: [new VueLoaderPlugin() //] }复制代码
客户端webpack配置
const path = require('path'); cosnt HtmlWebpackPlugin = require('html-webpack-plugin');const { merge } = reqire('webpack-merge');const base = require('./webpack.base.js');module.exports = (base, {entry: {client: path.resolve(__dirname, './src/client-entry.js') },plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, './public/index.html'),filename: 'client.html'}) ] })复制代码
服务端webpack配置
const path = require('path'); cosnt HtmlWebpackPlugin = require('html-webpack-plugin');const { merge } = reqire('webpack-merge');const base = require('./webpack.base.js');module.exports = merge(base, {target: 'node', //目标是给node使用entry: {server: path.resolve(__dirname, './src/server-entry.js') },output: {libraryTarget: 'commonjs2' //让打包后的server.bundle.js用module.exports导出},plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, './public/index.ssr.html'),filename: 'server.html',excludeChunks: ['server'], //打包后server.html中不引入server.bundle.jsmimyfy: false, //不压缩,防止<!--vue-ssr-outlet-->被覆盖}) ] })复制代码
index.ssr.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--><script src="./client.bundle.js"></script></body></html>复制代码
index.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><div id="app"></div></body></html>复制代码
main.js
const Vue from 'vue';const App from './App.vue';export default () => {const app = new Vue({render: h => h(App) })return { app } }复制代码
client-entry.js
const createApp from './main.js';const { app } = createApp(); app.$mount('#app');复制代码
server-entry.js
const createApp from './main.js';export default () => {const { app } = createApp();return app; }复制代码
server.js
const Koa = require('koa');const Router = require('koa-router');const static = require('koa-static');const VueServerRenderer = require('vue-server-renderer');const fs = require('fs');const path = require('path');const app = new Koa(); cont router = new Router();const serverBundle = fs.readFileSync(path.resolve(__dirname, './dist/server.bundle.js'));const template = fs.readFileSync(path.resolve(__dirname, './dist/server.html'));const render = VueServerRenderer.createBundleRenderer(serverBundle, { template }) router.get('/', async (ctx) => { ctx.body = new Promise((resolve, reject) => { render.renderToString((err, html) => {if (err) reject(err); resolve(html); }) }) }) app.use(route.routes()) app.use(static(path.resolve(__dirname, './dist'))); app.listen(3000, () => {console.log('start server success'); })复制代码
App.vue
这里需要注意的是,在最外层需要添加一个 id='app' , ssr官网上有说 客户端激活 , data-server-rendered 特殊属性,让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的,并且应该以激活模式进行挂载。注意,这里并没有添加 id="app",而是添加 data-server-rendered 属性:你需要自行添加 ID 或其他能够选取到应用程序根元素的选择器,否则应用程序将无法正常激活,
<template> <div id='app'> <Foo></Foo> <Bar></Bar> </div> </template> <script> import Bar from "./components/Bar.vue"; import Foo from "./components/Foo.vue"; export default { components: { Foo, Bar, }, }; </script>复制代码
Bar.vue
<template> <div>bar</div> </template> <style scoped="true"> div{ background:red } </style>复制代码
Foo.vue
<template> <div @click="show">foo</div> </template> <script> export default { methods:{ show(){ alert(1) } } } </script>复制代码