feat(esm): Vendor 把装好的 npm 包连同依赖摊平成单文件
解决的是「脚本要用第三方库,但目标机器上没有 node」。
npm 真正干的活是解析依赖树——读 semver 范围、查注册表定版本、递归、处理冲突。
这步绕不开,得在有 node 的机器上做一次。但做完之后依赖树就是死数据了,用
esbuild 摊平成一个文件,发布物里只带那一个就够:
qs v6.16.0 打进 47 个文件 -> 73.6 KB (原 19 个包 1.7 MB)
es-toolkit v1.52.0 打进 219 个文件 -> 51.9 KB
产物是最小的 node_modules 布局,脚本照常 import,写法完全不变。
两个实现细节:
- 入口不能直接写包名,esbuild 的 EntryPoints 是文件路径。所以造一段转发
源码当 stdin 入口,包名放进 import,才走正常的 node_modules 解析。
- 转发源码里写 export { default } 时,只有具名导出的包会报错(ESM 原生的
很多是这样),退回去用只带具名导出的版本重打一次。
打包目标从 Loader 里提成了共用常量:摊平出来的库必须跟脚本同一档,
否则库能打出脚本引擎跑不了的语法。
零依赖的包不用这个——直接下 tarball 解开就行,README 里记了命令。
This commit is contained in:
@@ -201,6 +201,43 @@ stat 一遍太贵。装完或换版本后调 `loader.Rebuild()`。
|
||||
|
||||
> esbuild 只**解析** `node_modules`,不下载。包怎么进去(npm/pnpm 安装、直接提交进仓库)由你决定。
|
||||
|
||||
### 目标机器上没有 node 怎么办
|
||||
|
||||
`node_modules` 对 esbuild 来说就是一个约定好的目录布局,谁摆进去的不重要。所以:
|
||||
|
||||
**零依赖的包**直接下 tarball 就行,全程只用 curl 和 tar:
|
||||
|
||||
```sh
|
||||
curl -sL https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz -o /tmp/x.tgz
|
||||
mkdir -p app/node_modules/es-toolkit
|
||||
tar -xzf /tmp/x.tgz -C app/node_modules/es-toolkit --strip-components=1
|
||||
```
|
||||
|
||||
npm 的 tarball 里固定是一个 `package/` 目录,`--strip-components=1` 剥掉就是标准布局。
|
||||
|
||||
**有依赖的包**得先解析依赖树——那正是 npm 真正在干的活(读 semver 范围、查注册表
|
||||
定版本、递归、处理冲突),手工做不现实。但这一步只需要在**有 node 的机器上做一次**,
|
||||
之后用 `Vendor` 把整棵树摊平成一个文件:
|
||||
|
||||
```go
|
||||
res, err := esm.Vendor(".", "qs", "app/node_modules")
|
||||
// qs v6.16.0 打进 47 个文件 -> 73.6 KB(原 node_modules 19 个包 1.7 MB)
|
||||
```
|
||||
|
||||
产物是最小布局,脚本照常 `import qs from "qs"`:
|
||||
|
||||
```
|
||||
app/node_modules/qs/
|
||||
├── index.mjs 依赖全内联
|
||||
└── package.json 只为让解析器找到入口
|
||||
```
|
||||
|
||||
**发布物里带上这两个文件,目标机器不需要 node、不需要 npm、不需要联网。**
|
||||
|
||||
两个 `package.json` 别混:**脚本根目录那个不需要**(esbuild 不看它,`type: module`
|
||||
也不影响);**包自己那个必需**——解析器靠 `main`/`module` 定位入口,拿掉就打包失败。
|
||||
零依赖的包那个跟着 tarball 自带,`Vendor` 摊平的那个由它生成。
|
||||
|
||||
### 挑第三方库要看两件事
|
||||
|
||||
**一、有没有用 goja 没有的全局。** esbuild 的 `target` 只降级**语法**,不补**全局对象**。
|
||||
|
||||
Reference in New Issue
Block a user