Vue活动页面改成预渲染模式

需求

一个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-pluginnpm 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-routernpm 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+),所以如果不能翻墙,下载的时候就报错了。

可以使用如下方法解决

  1. 使用Chromium国内源
npm config set puppeteer_download_host=https://npm.taobao.org/mirrors npm i puppeteer
  1. 使用淘宝cnpm安装
npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm i puppeteer
  1. 手动下载Chromium文件,解压后放在本地。下载地址
  • 下载之前先打开puppeteerpackage.json文件,看看文件中的chromium_revision是多少。下载相对应系统的对应版本
  • 下载后放到模块的默认读取目录下node_modules\puppeteer\.local-chromium\win64-526987(系统类型-版本号)\chrome-win32(下载的文件名)\
  • 放在其他目录,运行时设置路径参数puppeteer.launch({executablePath:'ChromiumExePath'})

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: