跳到主要内容

http-import-plugin

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

一、认识


Esbuild 原生不支持通过 HTTPCDN 服务上拉取对应的第三方依赖资源,如下代码所示:

// 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