webpack5, vue3,vue-cli5 使用预渲染

1,488 阅读3分钟

背景

环境: webpack5, vue3,vue-cli5
插件: prerender-spa-plugin
报错: [prerender-spa-plugin] Unable to prerender all routes!

 ERROR  Failed to compile with 1 error
 error  [prerender-spa-plugin] Unable to prerender all routes!

 ERROR  Error: Build failed with errors.

两种解决方案

  1. node_modules\prerender-spa-plugin\es6\index.js:60
    compilerFS.mkdirp函数改成compilerFS.mkdirmkdirp.png

  2. 改用prerender-spa-plugin-next插件
    注意这个插件配置项和prerender-spa-plugin不太一样,不能照抄。

故事开始

  某天,我在愉快地学(摸)习(鱼),突然又双叒叕被抓去铲屎(T_T),要把之前做的一个东西加入SEO。

  然鹅,这个是静态项目,没有跑在node上,也就(没)用(时)不(间)了(改)ssr...
行叭,Prerender 走一个~

  然后...我就收到报错大礼包一份。

error.png   吓得我立马跑去检查配置文件,并自我怀疑:作为一只 CV 攻城狮,配置文件这玩意通常都是老项目拉过来的,除了开荒的时候改过,后面基本都不会再动它,应该没什么问题把...
然后发现配置好像确实没什么问题,场面一度十分尴尬。

  好吧,人肉 debug 开始!我熟练地点进 prerender-spa-plugin 里(熟练到让人心疼)
一顿搜索 Unable to prerender all routes 出来两个文件调用,分别是 ES5ES6 的规范,到 index.js 瞄一眼,就能发现 Node.js 版本在8.0以上使用 ES6 版。

  戳进去发现,抛出的并不是原始错误,原始错误已经被吃掉了(这...)。 code.png   打个断点,发现是函数 compilerFS.mkdirp 调用出错。 errMsg.png   一键飞过去看看调用的地方,发现 compilerFS 其实就是 compiler.outputFileSystem,里面确实是没有这个方法,熟悉 Node.jswebpakc 的大佬们这时候脸上应该已经露出了和煦的微笑。

fn.png

  作者很贴心的给了个mkdirp-promise的链接,戳进去就能看见这么一句话: doc.png

   mkdirp-promise 已经停更3年,并且作者近期发话已经弃用,也用不了了。如果你使用的是最新的 Node.js 那么直接用 fs.mkdir 就行了。
现在,把node_modules\prerender-spa-plugin\es6\index.js60compilerFS.mkdirp函数改成compilerFS.mkdir

  // From https://github.com/ahmadnassri/mkdirp-promise/blob/master/lib/index.js
  const mkdirp = function (dir, opts) {
    return new Promise((resolve, reject) => {
      // compilerFS.mkdirp -> compilerFS.mkdir
      compilerFS.mkdir(dir, opts, (err, made) => err === null ? resolve(made) : reject(err))
    })
  }

再来一次~

success.png

完结撒花~~~

故事背后的故事

   compiler.outputFileSystem 其实就是 Node.js 的输出文件系统,调用的是 Node.jsfs 模块,
fs.mkdir 这包是给早期 Node.js 递归创建文件夹,并且返回 Promise 用的,现在的 Node.js 早已经支持递归,并且可以返回Promise。所以这个包已经不需要了,直接用fs.mkdir就行了。

prerender-spa-plugin配置

// vue.config.js
const path = require("path")
const PrerenderSPAPlugin = require("prerender-spa-plugin")
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

module.exports = {
  // ...
  configureWebpack(config) {
    if (process.env.NODE_ENV === "production") {
      const prerenderCfg = new PrerenderSPAPlugin({
        indexPath: path.join(__dirname, "dist", "index.html"),
        staticDir: path.join(__dirname, "dist"),
        outputDir: path.join(__dirname, "dist", "prerender"),
        routes: ["/", "/about"],
        // 自定义触发预渲染的事件
        // SPA根元素挂载之后 document.dispatchEvent(new Event("prerender"))抛出 prerender 事件触发预渲染
        renderer: new Renderer({ renderAfterDocumentEvent: "prerender" })
      })
      config.plugins.push(prerenderCfg)
    }
  }
}

prerender-spa-plugin-next 配置


三点几了.jpg
三点几了!饮茶先啦~~
prerender-spa-plugin-next 的配置先咕了,下次再讲 本萌新先去饮茶啦~


2021.5.17,补充 prerender-spa-plugin-next 配置

const path = require("path")
const PrerenderSPAPlugin = require("prerender-spa-plugin-next")
// 可选
const renderer = require("@prerenderer/renderer-puppeteer")
module.exports = {
  lintOnSave: "warning",
  publicPath: "/",
  assetsDir: "static",
  configureWebpack (config) {
    if (process.env.NODE_ENV === "production") {
      const prerenderCfg = new PrerenderSPAPlugin({
        routes: ["/", "/about"],
        // 可选
        renderer,
        postProcess (context) {
          context.outputPath = path.join(
            // 不要拼__dirname,最终会转成绝对路径,拼了反而出错。
            // PS: 为什么要加一层文件夹? 因为默认写入首页叫index.html, 不加会导致写入冲突 ——_——#
            "rerender",
            (context.route.replace("/", "") || "index") + ".html"
          )
          return context;
        },
        renderOptions: {
          renderAfterDocumentEvent: "prerender",
        },
      })
      config.plugins.push(prerenderCfg)
    }
  },
}