# 预渲染
Vue 官方提供了 PrerenderSPAPlugin
(opens new window) 插件来实现预渲染。使用方式如下:
// 首先,这是插件的入口
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
plugins: [
new PrerenderSPAPlugin({
// 必需 - webpack 打包输出路径
staticDir: path.join(__dirname, 'dist'),
// 可选 - 渲染后输出的目录位置,默认就是 staticDir 目录
outputDir: path.join(__dirname, 'prerendered'),
// 可选 - 启动的主页
// 默认就是 打包输入路径里的 `index.html`
indexPath: path.join(__dirname, 'dist', 'index.html'),
// 必需 - 要预渲染哪些页面
routes: [ '/', '/about', '/some/deep/nested/route' ],
// 可选 - 允许在内容在最终写入到文件之前进行定义的修改,postProcess 提供一个参数 renderedRoute
// renderedRoute 应该是与当前渲染内容相关的信息,通过修改它还修改最终输出的内容
// renderedRoute 包含的属性:
// {
// route: String, // Where the output file will end up (relative to outputDir)?
// originalRoute: String, // The route that was passed into the renderer, before redirects.
// html: String, // 当前路径下的 HTML
// outputPath: String // 当路由页面的输出路径
// }
postProcess (renderedRoute) {
// 忽略任何重定向
renderedRoute.route = renderedRoute.originalRoute
// 删除空格. (Don't use this in production)
renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
// 如果目录名以.html文件扩展名结尾,则从输出路径中删除/index.html.
// 例如: /dist/dir/special.html/index.html -> /dist/dir/special.html
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route)
}
return renderedRoute
},
// 可选 - Uses html-minifier (https://github.com/kangax/html-minifier)
// 输出渲染结果的优化配置.
// Option reference: https://github.com/kangax/html-minifier#options-quick-reference
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
// Server configuration options.
// server 属性在最终输出的纯静态的页面中是不起作用的,他的作用仅限于你项目中有数据请求是从接口哪里拿到的
// 防止插件运行过程中因为数据拿不到导致报错,渲染失败情况
server: {
// Normally a free port is autodetected, but feel free to set this if needed.
port: 8001,
// proxy 接口代理,与vue的devServer相同
proxy:{
'/api': {
target:'http://www.lanjz.com',
changeOrigin:true, //是否跨域
},
}
},
// 实际使用的渲染器
// Available renderers: https://github.com/Tribex/prerenderer/tree/master/renderers
renderer: new Renderer({
// 可选 - 要注入到全局环境的中变量名,这个变量名的值为下面 inject 配置内容
injectProperty: '__PRERENDER_INJECTED',
// 可选 - __PRERENDER_INJECTED 的值.
inject: {
foo: 'bar'
},
// 可选 - 默认为0,没有限制
// 异步渲染路由.
// 使用它来限制并行渲染的路由数量
maxConcurrentRoutes: 4,
// 可选 - 在特定的事件触发后再开始渲染
// 如:当前配置表示执行 document.dispatchEvent(new Event('custom-render-trigger')) 事件后再显示内容
renderAfterDocumentEvent: 'custom-render-trigger',
// 可选 - 检测到特别元素时再呈现内容。 使用 `document.querySelector`
renderAfterElementExists: 'my-app-element',
// 可选 - 等待多久时间后再渲染
// 不推荐使用
renderAfterTime: 5000, // Wait 5 seconds.
// Other puppeteer options.
// (See here: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions)
headless: false // Display the browser window when rendering. Useful for debugging.
})
})
]
}
# 工作方式
PrerenderSPAPlugin 插件另启动一个 webpack-dev-server
服务,将完整的项目运行在无头浏览器中,再使用 puppeteer
把对应路由的页面爬取下来
# 实战
添加 Webpack 配置
以 vue.config.js
为例:
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
configureWebpack: {
output: {
// 微应用的包名,这里与主应用中注册的微应用名称一致
library: "VueMicroApp",
// 将你的 library 暴露为所有的模块定义下都可运行的方式
libraryTarget: "umd",
// 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
jsonpFunction: `webpackJsonp_VueMicroApp`,
},
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, './dist'),
// 对应自己的路由文件,比如index有参数,就需要写成 /index/param1。
routes: ['/vue/home'], // 因为该系统操作都是基于登录后的,所以只做登录页面的预渲染就行了
renderer: new Renderer({
headless: false,
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event',
}),
})
]
},
};
上面插件配置中,添加了配置项 enderAfterDocumentEvent: 'render-event'
,表示在页面触发 render-event
后再开始渲染。为了正确抓取到完整的页面应该在页面加载完再开始预渲染,所以需要在页面加载完成后再触发对应的事件
在应用中添加开始预渲染的事件
在入口 App.vue
的 mounted
钩子中添加事件
<template>
<router-view></router-view>
</template>
<script>
export default {
mounted() {
document.dispatchEvent(new Event('render-event'))
}
}
</script>
正常的话重新打包就可以生预渲染页面
如果需要添加对应的 keywords
, description
等信息,可以配置 vue-meta-info
配置使用
yarn add vue-meta-info
import MetaInfo from 'vue-meta-info'
app.use(MetaInfo)
// 需要添加 meta 的组件添加钩子
export default {
mounted() {
},
metaInfo: {
title: 'My Example App', // set a title
meta: [{ // set meta
name: 'keyWords',
content: 'My Example App'
}]
}
}
# 预渲染和服务端渲染
预渲染我理解的话很像静态网站生成器的,所以如果页面基本静态且涉及的接口数据少的话挺合适的。
本质相当于为打包出的 html
页面在挂载元素中添加了 DOM 内容,这样渲染的时候就可以马上呈现出容,之后加载 JS 脚本后,挂载元素再被 vue
动态渲染的内容所代替
服务端渲染
服务端渲染时页面的内容渲染是在服务端进行的,客户端加载后不会重新加载 DOM 元素,这一点应该就是最主要的区别了
← Diff