html-plugin
2023年12月19日
一、认识
Esbuild
作为一个前端打包工具,本身并不具备 HTML
的构建能力。也就是说,当它把 js/css
产物打包出来的时候,并不意味着前端的项目可以直接运行了,我们还需要一份对应的入口 HTML
文件。而这份 HTML
文件当然可以手写一个,但手写显得比较麻烦,尤其是产物名称带哈希值的时候,每次打包完都要替换路径。那么,我们能不能通过 Esbuild
插件的方式来自动化地生成 HTML
呢?
html-plugin
逻辑如下:
-
在
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": { /* 省略内容 */ }
}
} -
从
outputs
属性中我们可以看到产物的路径,这意味着我们可以在插件中拿到所有js
和css
产物,然后自己组装、生成一个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
已经成功输出到根目录。