跳到主要内容

html-plugin

2023年12月19日
柏拉文
越努力,越幸运

一、认识


Esbuild 作为一个前端打包工具,本身并不具备 HTML 的构建能力。也就是说,当它把 js/css 产物打包出来的时候,并不意味着前端的项目可以直接运行了,我们还需要一份对应的入口 HTML 文件。而这份 HTML 文件当然可以手写一个,但手写显得比较麻烦,尤其是产物名称带哈希值的时候,每次打包完都要替换路径。那么,我们能不能通过 Esbuild 插件的方式来自动化地生成 HTML 呢?

html-plugin 逻辑如下:

  1. Esbuild 插件的 onEnd 钩子中可以拿到 metafile 对象的信息:

    {
    "inputs": { /* 省略内容 */ },
    "output": {
    "dist/index.js": {
    imports: [],
    exports: [],
    entryPoint: 'src/index.jsx',
    inputs: {
    'http-url:https://cdn.skypack.dev/-/object-assign@v4.1.1-LbCnB3r2y2yFmhmiCfPn/dist=es2019,mode=imports/optimized/object-assign.js': { bytesInOutput: 1792 },
    'http-url:https://cdn.skypack.dev/-/react@v17.0.1-yH0aYV1FOvoIPeKBbHxg/dist=es2019,mode=imports/optimized/react.js': { bytesInOutput: 10396 },
    'http-url:https://cdn.skypack.dev/-/scheduler@v0.20.2-PAU9F1YosUNPKr7V4s0j/dist=es2019,mode=imports/optimized/scheduler.js': { bytesInOutput: 9084 },
    'http-url:https://cdn.skypack.dev/-/react-dom@v17.0.1-oZ1BXZ5opQ1DbTh7nu9r/dist=es2019,mode=imports/optimized/react-dom.js': { bytesInOutput: 183229 },
    'http-url:https://cdn.skypack.dev/react-dom': { bytesInOutput: 0 },
    'src/index.jsx': { bytesInOutput: 178 }
    },
    bytes: 205284
    },
    "dist/index.js.map": { /* 省略内容 */ }
    }
    }
  2. outputs 属性中我们可以看到产物的路径,这意味着我们可以在插件中拿到所有 jscss 产物,然后自己组装、生成一个 HTML,实现自动化生成 HTML 的效果。

二、实现


const fs = require("fs/promises");
const path = require("path");
const { createScript, createLink, generateHTML } = require('./util');

module.exports = () => {
return {
name: "esbuild:html",
setup(build) {
build.onEnd(async (buildResult) => {
if (buildResult.errors.length) {
return;
}
const { metafile } = buildResult;
// 1. 拿到 metafile 后获取所有的 js 和 css 产物路径
const scripts = [];
const links = [];
if (metafile) {
const { outputs } = metafile;
const assets = Object.keys(outputs);

assets.forEach((asset) => {
if (asset.endsWith(".js")) {
scripts.push(createScript(asset));
} else if (asset.endsWith(".css")) {
links.push(createLink(asset));
}
});
}
// 2. 拼接 HTML 内容
const templateContent = generateHTML(scripts, links);
// 3. HTML 写入磁盘
const templatePath = path.join(process.cwd(), "index.html");
await fs.writeFile(templatePath, templateContent);
});
},
};
}

// util.js
// 一些工具函数的实现
const createScript = (src) => `<script type="module" src="${src}"></script>`;
const createLink = (src) => `<link rel="stylesheet" href="${src}"></link>`;
const generateHTML = (scripts, links) => `
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Esbuild App</title>
${links.join("\n")}
</head>

<body>
<div id="root"></div>
${scripts.join("\n")}
</body>

</html>
`;

module.exports = { createLink, createScript, generateHTML };

三、配置


const html = require("./html-plugin");

// esbuild 配置
plugins: [
// 省略其它插件
html()
],

四、测试


执行 node build.js 对项目进行打包,你就可以看到 index.html 已经成功输出到根目录。