http-import-plugin
2023年12月19日
一、认识
Esbuild
原生不支持通过 HTTP
从 CDN
服务上拉取对应的第三方依赖资源,如下代码所示:
// src/index.jsx
// react-dom 的内容全部从 CDN 拉取
// 这段代码目前是无法运行的
import { render } from "https://cdn.skypack.dev/react-dom";
import React from 'https://cdn.skypack.dev/react'
let Greet = () => <h1>Hello, juejin!</h1>;
render(<Greet />, document.getElementById("root"));
现在我们需要通过 Esbuild
插件来识别这样的 url
路径,然后从网络获取模块内容并让 Esbuild
进行加载,甚至不再需要 npm install
安装依赖了
二、实现
module.exports = () => ({
name: "esbuild:http",
setup(build) {
let https = require("https");
let http = require("http");
// 1. 拦截间接依赖的路径,并重写路径
// tip: 间接依赖同样会被自动带上 `http-url`的 namespace
build.onResolve({ filter: /.*/, namespace: "http-url" }, (args) => ({
// 重写路径
path: new URL(args.path, args.importer).toString(),
namespace: "http-url",
}));
// 2. 通过 fetch 请求加载 CDN 资源
build.onLoad({ filter: /.*/, namespace: "http-url" }, async (args) => {
let contents = await new Promise((resolve, reject) => {
function fetch(url) {
console.log(`Downloading: ${url}`);
let lib = url.startsWith("https") ? https : http;
let req = lib
.get(url, (res) => {
if ([301, 302, 307].includes(res.statusCode)) {
// 重定向
fetch(new URL(res.headers.location, url).toString());
req.abort();
} else if (res.statusCode === 200) {
// 响应成功
let chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => resolve(Buffer.concat(chunks)));
} else {
reject(
new Error(`GET ${url} failed: status ${res.statusCode}`)
);
}
})
.on("error", reject);
}
fetch(args.path);
});
return { contents };
});
},
});
三、配置
const { build } = require("esbuild");
const httpImport = require("./http-import-plugin");
async function runBuild() {
build({
absWorkingDir: process.cwd(),
entryPoints: ["./src/index.jsx"],
outdir: "dist",
bundle: true,
format: "esm",
splitting: true,
sourcemap: true,
metafile: true,
plugins: [httpImport()],
}).then(() => {
console.log("🚀 Build Finished!");
});
}
runBuild();
四、测试
执行 node build.js
,发现依赖已经成功下载并打包了。
Preview