怎么使用prerender-spa-plugin插件对页面进行预渲染呢

目录
文章目录隐藏
  1. 背景
  2. 现状
  3. 目标
  4. 方案
  5. 本地验证
  6. FAQ
  7. 总结

怎么使用 prerender-spa-plugin 插件对页面进行预渲染呢

本文主要是介绍使用prerender-spa-plugin插件在针对前端代码进行预渲染。

预渲染(SSG)和服务端(SSR)渲染有一定的区别,大家想要了解的话可以看:传送门

背景

因为之前的网站是使用 Vue 开发的,这种前端 JavaScript 渲染的开发模式,对于搜索引擎来说非常的不友好,没有办法抓取到有效的信息。因此为了进行 SEO,我们需要对页面进行一些预渲染。

预渲染比较适合静态或者变化不大的页面,能够通过部署前的一次静态渲染,将页面上大部分内容都渲染出来。这样搜索引擎在爬取的时候,就能够爬到相关的内容信息。

现状

目前商企通官网情况列举如下:

  • 技术栈使用的是 Vue,脚手架使用的是 vue-cli,使用 JavaScript 前端渲染方案(这个方案对技术栈没有要求,兼容所有方案)
  • 发布工具使用的是公司的工具,打包过程中,HTML 资源传递到 A 域名下,CSS、JS、Image 等资源传递到 B 域名下。

目标

希望能够通过预渲染,让页面在初次访问没有执行 JavaScript 时,就能够携带足够的信息,即将 JavaScript 渲染的内容提前渲染到 HTML 中。

发布期望不做过多的修改。

方案

我们本次方案主要采用的是prerender-spa-plugin这个 webpack 的插件来实现的。

它的主要原理是启动浏览器,渲染完成后抓取 HTML,然后再替换掉原有 HTML。

我们需要实现预渲染,那么我们需要完成以下几件事情:

  1. 插件引入和配置。
  2. 本地验证。
  3. 改造打包构建流程。
  4. 线上验证。

下面,我们一个一个来说下,我们如何做这个事情的。

插件引入和配置

首先,我们需要引入一个预渲染插件,执行命令:

cnpm i prerender-spa-plugin -D

这个命令除了安装插件本身以外,依赖了 puppeteer,然后 puppeteer 又依赖落地 chromium,所以最后我们其实是需要在依赖中安装一个 chromium。

如果大家安装 puppeteer 非常慢或者经常失败,可以参考下这文章中的方法:国内下载安装-Puppeteer-的方法,指定 puppeteer 下载镜像。

安装完成后,我们就可以在 webpack 的配置文件中增加对应的配置了。

如果大家使用的也是 vue-cli,那么我们需要增加的配置是在 vue.config.js 中,如果是直接修改 webpack 的配置,那么方法也是类似。

下面我们以 vue.config.js 的修改为例:

const PrerenderSPAPlugin = require('prerender-spa-plugin');

module.exports = {
  ...,
  configureWebpack: {
    ...,
    chainWebpack: config => {
      config.plugin('prerender').use(PrerenderSPAPlugin, [
        {
          staticDir: path.join(__dirname, 'build'),
          routes: [
            '/',
            '/product',
            '/case',
            '/about',
            '/register',
          ],
          renderer: new Renderer({
            headless: true,
            executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
            // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
            renderAfterDocumentEvent: 'render-event',
          }),
        },
      ]);
    }
  }
}

因为我们在项目中使用了webpack-chain,所以我们的语法是上面类似链式调用的方法。如果大家直接修改的话,就是采用 vue 的原来的修改配置的方式。

下面我简单的给大家介绍下,上面的一些配置的含义:

  • staticDir:这个指的是输出预渲染文件的目录。
  • routes:这个指的是需要预渲染的路由。这里需要注意的是,vue 的hash路由策略是没有办法进行预渲染的,所以如果要进行预渲染,需要改成history路由,然后预渲染后会变成多个 HTML 文件,每个文件都带全量路由功能,只是默认路由不一样而已。
  • renderer:这个是可以传入的 puppeteer 的配置,我说下我用过的这几个配置:

headless:是否使用headless模式渲染,建议选择 true。

executablePath:指定chromium的路径(也可以是 chrome)。这个配置在 talos 中是需要指定的,talos 中的 chrome 地址默认是/usr/bin/google-chrome

renderAfterDocumentEvent:这个的意思是在哪个事件触发后,进行预渲染的抓取。这个事件是需要在代码中自己使用 dispatchEvent 来触发的,这样自己可以控制预渲染的时机。一般我们都是在最外层的组件的 mounted 钩子中触发,如果大家有其他需求也可以自己指定。

更多的可以看插件的官方文档

开发完成后,我们可以在本地构建一次,看看是否能够生成符合我们预期的代码。

vue.config.js 指定 publicPath 导致预渲染失败问题

如果大家和我这个项目一样,在 vue.config.js 中传入publicPath指定第三方 CDN 域名,会将 CSS、JavaScript、Image 等资源传递到不同的域名上,类似配置如下:

module.exports = {
  ...,
  publicPath: `//awp-assets.cdn.net/${projectPath}`,
  ...,
};

如果没有预渲染,这种方案会在打包完成后分别上传至不同的 CDN 域名,在线上访问是没有问题的。

但是在本地,这个时候 CSS 和 JS 资源还没有上传到 CDN 中,浏览器无法加载对应的资源进行页面的渲染,这样的话会导致本地预渲染失败。

为了解决这个问题,有两个解决思路。

  1. 【推荐】调整打包的策略,将非 HTML 资源也上传至同一个 CDN 域名下,这样的话,我们就可以使用相对路径来访问这些资源,不需要传递新域名给 publicPath,这样我们在本地构建的时候就可以访问到这些值。这个是个比较靠谱合理的方法,比较推荐。
  2. (如果上面那个方法实在无法实现,那么可以考虑这个方案)在预渲染之前,资源是在本地可以通过相对路径访问到的,这个时候使用替换的方式把 HTML 中的资源文件地址替换掉,然后预渲染完成后再替换回来。这个方法比较 hack,但是经过实际验证确实是可以生效。具体的做法是自己写一个简单的 webpack 插件。

首先,我们需要安装一个新的 NPM 包,用来对文件中的内容进行替换(自己写正则也可以,不过用这个会方便一些),具体命令如下:

mnpm i replace-in-file

安装后,我们需要增加两个 webpack 的插件,分别作用在afterEmitdone这两个钩子节点上。如果想要了解为什么是这两个钩子节点,那么你可以阅读下 webpack 插件的开发章节。

const replace = require('replace-in-file');

let publicPath = `//awp-assets.cdn.net/${projectPath}`;

// 第 1 个替换插件,主要是将原先打包过程中带有 CDN 域名的路径替换成相对路径
function ReplacePathInHTMLPlugin1(cb) {
  this.apply = compiler => {
    if (compiler.hooks && compiler.hooks.afterEmit) {
      compiler.hooks.afterEmit.tap('replace-url', cb);
    }
  };
}

function replacePluginCallback1() {
  replace({
    files: path.join(__dirname, '/build/**/*.html'),
    from: new RegExp(
      publicPath.replace(/([./])/g, (match, p1) => {
        return `\\${p1}`;
      }),
      'g'
    ),
    to: '',
  })
    .then(results => {
      console.log('replace HTML static resources success', results);
    })
    .catch(e => {
      console.log('replace HTML static resources fail', e);
    });
}

// 第 2 个替换插件,主要是将预渲染后的 HTML 文件中的相对路径替换成带有 CDN 域名的路径
function ReplacePathInHTMLPlugin2(cb) {
  this.apply = compiler => {
    if (compiler.hooks && compiler.hooks.done) {
      compiler.hooks.done.tap('replace-url', cb);
    }
  };
}

function replacePluginCallback2() {
  replace({
    files: path.join(__dirname, '/build/**/*.html'),
    from: [/href="\/css/g, /href="\/js/g, /src="\/js/g, /href="\/favicon.ico"/g],
    to: [
      `href="${publicPath}/css`,
      `href="${publicPath}/js`,
      `src="${publicPath}/js`,
      `href="${publicPath}/favicon.ico"`,
    ],
  })
    .then(results => {
      console.log('replace HTML static resources success', results);
    })
    .catch(e => {
      console.log('replace HTML static resources fail', e);
    });
}

上述代码就是我们需要增加的两个 webpack 的替换插件和对应的回调函数,接下来我们看下在 webpack 中怎么配置。

module.exports = {
  publicPath,
  outputDir,
  crossorigin: 'anonymous',
  chainWebpack: config => {
    config.plugin('replaceInHTML').use(new ReplacePathInHTMLPlugin1(replacePluginCallback));
    config.plugin('prerender').use(PrerenderSPAPlugin, [
      {
        staticDir: path.join(__dirname, 'build'),
        // 我们应该只会使用根路径,因为是 hash 路由,所以其他页面预渲染没有意义,因此不进行预渲染
        routes: ['/'],
        renderer: new Renderer({
          headless: true,
          executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
          // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
          renderAfterDocumentEvent: 'render-event',
        }),
      },
    ]);
    config.plugin('replaceInHTML2').use(new ReplacePathInHTMLPlugin2(replacePluginCallback2));
  }

我们第一个替换插件,需要在预渲染插件前执行,在预渲染插件执行前,将 HTML 中的资源的地址替换成本地的相对路径;第二个则需要在替换后执行,这样将预渲染后端资源中的相对路径,再替换成 CDN 地址。

通过这两个插件,我们就可以完成在预渲染前替换掉路径完成预渲染,然后在预渲染后再完成替换保证线上可用。

本地验证

通过上面的方式,我们应该已经得到了一个预渲染完成的 HTML,接下来我们就是要验证下这个 HTML 是否符合预期了。

比较简单的验证方式,可以直接访问那个 HTML 文件,或者启动一个 HTTP 静态资源服务来验证。

验证的话,你可以使用 curl 来进行请求,这种情况下 JavaScript 不会执行,你可以看到 HTML 的源文件是什么。

FAQ

  1. 在 chrome 版本比较低的情况下(比如 v73),会提示渲染失败?

这个是因为 chrome 的版本过低,导致预渲染失败。解决方案是升级 chrome/chromium 版本到最新(目前 v93 无问题)版本即可。

总结

如果我们需要实现 SSG(静态站点生成),那么我们可以使用 prerender-spa-plugin 这个插件来做,这个插件可以在本地启动 chromium 来抓取 HTML 内容,再写回 HTML 文件中,如我们我们需要对其中的静态资源文件进行处理,我们可以使用替换的插件,针对处理前后的内容进行替换,来达到我们的诉求。

直接替换压缩后代码虽然看起来有效,但是这个强依赖压缩的算法和内容顺序,强烈不推荐直接用脚本修改替换压缩后文件,最好是在 webpack 的 done 钩子回调中处理。

作者:hjava,原文:点击这里

「点点赞赏,手留余香」

2

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 怎么使用prerender-spa-plugin插件对页面进行预渲染呢

发表回复