需求
一个H5
活动页面,使用vue-cli
搭建。
生成的html
文件只有一个<div id="app"></div>
,所有的页面渲染都是通过js
完成的。首屏加载速度慢
生成的html
文件中需要有基础DOM,提高首屏加载速度
思路
服务器端渲染一般使用Nuxt.js
,然后通过npm run generate
生成静态文件。
这里是使用vue-cli
搭建的,迁移成本比较大。Nuxt.js
比较重,也不太适合这种很简单的H5
活动页面
参考:使用Nuxt.js改造活动页面
这里采用预渲染的方式。使用prerender-spa-plugin
进行预渲染
prerender-spa-plugin
内置了Puppetter
,用来模拟加载页面,抓取生成的代码。
Puppeteer
相关参考:Node.js - Puppeteer基本使用笔记
实现
基本使用
-
下载
prerender-spa-plugin
:npm i prerender-spa-plugin --save-dev
-
修改
vue.config.js
文件
const path = require('path')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : './',
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSpaPlugin({
// 生成文件的路径,也可以与webpakc打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(__dirname, 'dist'),
// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
routes: ['/']
})
]
}
}
}
- 执行打包命令即可
接口请求
我们的活动页面在进入的时候需要请求接口。
有的接口是通用的,有的接口跟url
上面的参数有关或者返回值会变化
通用的接口,我们可以在打包的时候就请求,然后写死在DOM中。方便快速显示。然后打开页面的时候再去重新请求更新数据
不通用的接口,在页面打开后再去请求
我们可以给Puppetter
注入一个变量,用来标识是不是预编译的过程,然后根据不同的情况处理
- 修改
vue.config.js
const path = require('path')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSpaPlugin.PuppeteerRenderer
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : './',
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSpaPlugin({
// 生成文件的路径,也可以与webpakc打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(__dirname, 'dist'),
// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
routes: ['/'],
renderer: new Renderer({
injectProperty: '__PRERENDER_INJECTED',
inject: {},
headless: true,
})
})
]
}
}
}
- 修改
vue
文件
// ...
export default {
// ...
async mounted() {
this.key = await initKey()
this.getGridData()
// 因为是注入到window中,所以不能在created生命周期处理
if (window.__PRERENDER_INJECTED) return
this.getUserInfo()
this.getExchangeList()
this.getAwardList()
},
// ...
}
渲染时机
我们也可以控制预编译渲染的时机,主要有以下三种:
renderAfterDocumentEvent
:等到事件触发去渲染,需要用户使用document.dispatchEvent(new Event('xxx'))
主动去触发,触发后才会开始渲染renderAfterElementExists
:等到dom元素出现时去渲染renderAfterTime
:xx毫秒后去渲染
当然我们也可以不用管渲染时机,让其自动渲染即可
这里以renderAfterDocumentEvent
为例
- 修改
vue.config.js
文件
const path = require('path')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSpaPlugin.PuppeteerRenderer
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : './',
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSpaPlugin({
// 生成文件的路径,也可以与webpakc打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(__dirname, 'dist'),
// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
routes: ['/'],
renderer: new Renderer({
injectProperty: '__PRERENDER_INJECTED',
inject: {},
headless: true,
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event'
})
})
]
}
}
}
- 修改
src/main.js
文件
// ...
new Vue({
render: h => h(App),
mounted() {
document.dispatchEvent(new Event('render-event'))
}
}).$mount('#app')
使用路由
prerender-spa-plugin
一般应用在使用路由的情况下
路由需要使用history
模式,hash
模式没有测试过
-
下载
vue-router
:npm i vue-router
-
添加
src/page.vue
文件,放置页面内容 -
修改
src/app.vue
文件
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
- 添加
src/router.js
文件
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
// component: () => import('./page.vue')
component: res => require(['@/page'], res)
}
]
})
- 修改
src/main.js
文件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// ...
new Vue({
router,
render: h => h(App)
// mounted() {
// document.dispatchEvent(new Event('render-event'))
// }
}).$mount('#app')
- 执行打包命令即可
当我们线上环境是http://xxx.com/index.html?k=xx
的时候,是没有问题的。
但H5
活动页面一般都在二级目录下,比如:http://xxx.com/xxx/index.html?k=xx
以上方式就会出现页面空白的情况。原因是因为使用了history
模式的路由,使用相对路径后出现找不到路由的问题。
可以通过配置通用路由来解决
- 修改
src/router.js
文件
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
// component: () => import('./page.vue')
component: res => require(['@/page'], res)
},
{
path: '*',
component: res => require(['@/page'], res)
}
]
})
常见问题
下载Chromium失败
prerender-spa-plugin
插件是需要依赖puppeteer
的,也就是谷歌出品的无头浏览器插件,这个插件会下载最新版的chromium
(大约200M+),所以如果不能翻墙,下载的时候就报错了。
可以使用如下方法解决
- 使用
Chromium
国内源
npm config set puppeteer_download_host=https://npm.taobao.org/mirrors
npm i puppeteer
- 使用淘宝
cnpm
安装
npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm i puppeteer
- 手动下载
Chromium
文件,解压后放在本地。下载地址
- 下载之前先打开
puppeteer
的package.json
文件,看看文件中的chromium_revision
是多少。下载相对应系统的对应版本- 下载后放到模块的默认读取目录下
node_modules\puppeteer\.local-chromium\win64-526987(系统类型-版本号)\chrome-win32(下载的文件名)\
- 放在其他目录,运行时设置路径参数
puppeteer.launch({executablePath:'ChromiumExePath'})
发表评论