rottenpen / blog

日常记录 blog,内容不限于前端,博文在 issue https://github.com/rottenpen/blog/issues
7 stars 0 forks source link

从实际项目需求中学习预渲染是怎么实现的 #29

Open rottenpen opened 3 years ago

rottenpen commented 3 years ago

背景

在开发vscode插件的过程中,我做了两套日志系统,一套是通过监听进程 output 封装的 vscode channel,但是这套日志系统还要负责监听不少其他来源的信息。所以我们做了第二套日志系统,通过左侧栏的 webview 展示专门用来可视化记录自动化测试进程日志的。 问题来了,vscode 对植入webview 有很多限制,静态资源需要在 html 字符串的路径上加入 vscode-resouce 协议,即原本路径是src="1.png" 要转成 src="vscode-resouce:1.png"。 通过

html = html.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g

我们仿佛解决了问题。 这时候新的问题来了,html 的 img 元素,我们都是通过 js 来生成的,这就导致我们的正则,无法修改img部分的路径,而 vscode 只会根据 html 提供的路径进行特殊处理。

正文

正菜来了,我们遇到的情况,就好像很多 spa 网页都会先通过预渲染生成页面来解决 seo 的问题(方法还有很多, 就不展开了)。 但是市面上大部分方案都是基于 webpack plugin 做的,我们并不需要用到,干脆没有轮子,自己来造吧。

  1. 老规矩看源码, github 搜 spa-prerender 选了6k+赞的第一个 prerender-spa-plugin,看到源码用到了 puppeteer 字眼的依赖。好的,懂了,不用继续看下去了。puppeteer 我们还不擅长嘛。
  2. 所以原理很简单,通过 puppeteer 的无头浏览器模式,先跑一下 html 代码,直接获取浏览器生成后的代码就搞定了。

马上动手撸

import * as puppeteer from "puppeteer-core";
const findChrome = require("carlo/lib/find_chrome");
let browser: puppeteer.Browser;

(async () => {
  let findChromePath = await findChrome({});
  let executablePath = findChromePath.executablePath;
  browser = await puppeteer.launch({
    executablePath,
    headless: true,
  });
})();
class Spider {
  async buildPage({ url, timeout = 500 }: { url: string; timeout: number }) {
    const page = await browser.newPage();
    await page.goto("file://" + url);

    if (timeout) {
      await page.waitFor(Number(timeout));
    }
    return page.content();
  }
}

export default new Spider();

这里用到了两个库,通过 carlo 可以寻找 Chrome 的绝对路径供 puppeteer-core 使用,这样我们就不需要下载 Chormium 增大包体积了。调用一下 buildPage 我们就可以马上得到我们想要的 html 内容啦。

小坑

但生成的 html 文件会有一点小问题,html 渲染出来后 js 代码动态生成的代码会跟静态生成 dom 重复,我们后续把对应的那几行 js 给切掉,就完事了。

小结