使用 prerender-spa-plugin 插件无法启动 chromium 进行预渲染

使用 prerender-spa-plugin 插件无法启动 chromium 进行预渲染

00x0 什么是预渲染

根据 Vue 文档描述来看,预渲染:即无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。

00x1 为什么需要预渲染

使用 Vue 技术开发的 SPA (单页应用程序 (Single-Page Application)) 对SEO不友好,如果项目有SEO的需求,需要使用服务器端渲染(SSR)或预渲染的方式来满足。对当前开发的项目而言,我们仅仅只有少数营销页面(例如 /, /about, /contact 等)需要SEO,所以选择采用预渲染的方式实现。点击这里查看 SSR预渲染 的比较。

00x2 预渲染工具库

prerender-spa-plugin

使用prerender-spa-plugin库把部分静态页面预渲染,prerender-spa-plugin库会依赖 google 的 puppeteer工具,在安装的时候,puppeteer会下载一个 chromium,大小约为140M,安装时,终端最好开启代理(梯子),如果没有梯子可能会安装失败或卡死。安装成功后,你会在本地项目路径中看到下载的 chromium:\node_modules\puppeteer\.local-chromium\win64-662092\chrome-win

ps:puppeteer 是 google chrome 团队官方开发的无界面(headless)chrome 工具,puppetter 可以实现生成网页页面的截图和PDF、抓取SSR、抓取网站内容、模拟登陆等

00x3 实现

windows 10 64位
node v10.15.3
npm 6.4.1
yarn 1.13.0
vue-cli 3.5.0
vue 2.6.6
vue-router 3.0.3
prerender-spa-plugin 3.4.0

3.0 安装 vue-cli 脚手架

npm install -g @vue/cli
# OR
yarn global add @vue/cli

更多实现请查看官方文档

3.1 安装 prerender-spa-plugin

打开初始化的项目,终端输入下面命令

npm install prerender-spa-plugin --save
3.2 配置 vue.config.js

项目根目录下如果没有 vue.config.js,则手动创建一个,存在则继续在 vue.config.js 文件中增加:

const path = require('path');
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;

module.exports = {
    configureWebpack: () => {
    if (process.env.NODE_ENV === "production") {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            // 【必须】生成文件的路径,也可以与webpakc打包的一致
            staticDir: path.join(__dirname, "dist"),
            // 【必须】对应自己的路由文件,如路由有参数,则需写成 /a/param1
            routes: ["/", "/about", "/news", "/contact"],
            //【可选】服务器端口配置
            server: {
              port: 8188
            },
            // 这个很重要,如果没有配置这段,也不会进行预编译
            renderer: new Renderer({
              inject: {
                foo: "bar"
              },
              headless: false,
              // 对应 src/main.js 中 document.dispatchEvent(new Event('custom-render-trigger')),两者的事件名称要相同
              renderAfterDocumentEvent: "custom-render-trigger"
            })
          })
        ]
      };
    }
  }
}

prerender-spa-plugin更多高级用法请查看官方文档

3.3 编辑 src/main.js
new Vue({
    router,
    render: h => h(App),
+ mounted () {
+    document.dispatchEvent(new Event('custom-render-trigger'))
+ }
}).$mount('#app')
3.4 配置router.js

将路由模式改为 mode: "history",因 prerender-spa-plugin 仅适用于使用 HTML5 的 history 路由,使用 hash 路由将不起作用。

3.5 打包预渲染

满心欢喜在终端敲下 npm run build,等待奇迹时,莫名其妙的坑出现了

00x4 “莫名其妙”的坑

运行 build 命令后,“莫名其妙”的坑如下:

> vue-cli-service build


-  Building for production...
{ TimeoutError: Timed out after 30000 ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r662092
    at Timeout.onTimeout (E:\github\DeGao-FrontEnd\node_modules\puppeteer\lib\Launcher.js:353:14)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10) name: 'TimeoutError' }
[Prerenderer - PuppeteerRenderer] Unable to start Puppeteer
(node:940) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'close' of null
    at PuppeteerRenderer.destroy (E:\github\DeGao-FrontEnd\node_modules\@prerenderer\renderer-puppeteer\es6\renderer.js:140:21)
    at Prerenderer.destroy (E:\github\DeGao-FrontEnd\node_modules\@prerenderer\prerenderer\es6\index.js:87:20)
    at PrerendererInstance.initialize.then.then.then.then.then.then.then.then.catch.err (E:\github\DeGao-FrontEnd\node_modules\prerender-spa-plugin\es6\index.js:144:29)
(node:940) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:940) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

为什么说是“莫名其妙”的坑,因为在这项目的前几天,使用预渲染的方式都非常正常,可以正常打包,输出静态文件。但是这个时候就抽风了,开发环境完全相同,当下有点摸不着头脑的感觉。

当然,遇坑则填坑,看终端打印的信息,分析可能有以下原因:

  • puppeteet 无法启动 chromium,权限不足?
  • puppeteet 下载的 chromium 版本错误?
  • puppeteet 下载的 chromium 文件损坏?

针对权限疑惑,检查了项目目录的相关权限,未发现有问题;接着到 chromium 站点下载 Win 64位的 r662092 版本包,重新覆盖 \node_modules\puppeteer\.local-chromium\win64-662092\chrome-win下的所有文件,重新 build,依旧输出同样错误。

解决不了,只有 Google 大法了,寻寻觅觅一圈依旧没有找到解决方法。当看到 Puppeteer Api 文档时,它提供一个属性 executablePath 人为指定运行绑定的 Chromium 版本默认情况下,Puppeteer 下载并使用特定版本的 Chromium 以及其 API 保证开箱即用。 如果要将 Puppeteer 与不同版本的 chrome 或 chromium 一起使用,在创建Browser实例时传入 chromium 可执行文件的路径即可。 如同看到了希望一样,回到 vue.config.js 文件中,添加 executablePath属性

module.exports = {
    configureWebpack: () => {
    if (process.env.NODE_ENV === "production") {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            // 【必须】生成文件的路径,也可以与webpakc打包的一致
            staticDir: path.join(__dirname, "dist"),
            // 【必须】对应自己的路由文件,如路由有参数,则需写成 /a/param1
            routes: ["/", "/about", "/news", "/contact"],
            //【可选】服务器端口配置
            server: {
              port: 8188
            },
            // 这个很重要,如果没有配置这段,也不会进行预编译
            renderer: new Renderer({
              inject: {
                foo: "bar"
              },
              headless: false,
              // 对应 src/main.js 中 document.dispatchEvent(new Event('custom-render-trigger')),两者的事件名称要相同
              renderAfterDocumentEvent: "custom-render-trigger",
              // 更改运行绑定的 Chromium 版本,即:使用本机 chrome 来执行预渲染操作,注意 win、mac、linux 不同环境的路径指定
              executablePath: 'Your/Path/Google/Chrome/Application/chrome.exe'
            })
          })
        ]
      };
    }
  }
}

重新运行 npm run build,成功打包输出 html 静态文件,算是暂时解决了这个“莫名其妙”的坑,不算完全解决上面出现的坑,后续再观望观望。

可能还喜欢下面的内容

解决 iOS 10.3.3 设备越狱后 Cydia 无法联网问题

解决 iOS 10.3.3 设备越狱后 Cydia 无法联网问题

5个简单步骤使用h3lix越狱iOS 10.3.3

5个简单步骤使用h3lix越狱iOS 10.3.3

Vultr 主机搭建 Ghost 博客

Vultr 主机搭建 Ghost 博客

ins@heyrock