关于javascript:如何使用prerenderspaplugin插件对页面进行预渲染

5次阅读

共计 5538 个字符,预计需要花费 14 分钟才能阅读完成。

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

预渲染(SSG)和服务端(SSR)渲染有肯定的区别,大家想要理解的话能够看:https://segmentfault.com/a/1190000023469150。

背景

因为之前的网站是应用 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. 线上验证。

上面,咱们一个一个来说下,咱们如何做这个事件的。

插件引入和配置

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

mnpm i prerender-spa-plugin -D

这个命令除了装置插件自身以外,依赖了 puppeteer,而后 puppeteer 又依赖落地 chromium,所以最初咱们其实是须要在依赖中装置一个 chromium。

如果大家装置 puppeteer 十分慢或者常常失败,能够参考下这个文档中的办法:https://brickyang.github.io/2019/01/14/ 国内下载安装 -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 的插件,别离作用在 afterEmit 和 done 这两个钩子节点上。如果想要理解为什么是这两个钩子节点,那么你能够浏览下 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 钩子回调中解决。

正文完
 0