如何让esbuild转换独立文件时引用已打包的bundle?
问题
Node包遵循规范将源码放在src/目录,新增cli/runner.ts作为二进制命令入口调用包内静态方法。使用esbuild构建时:
- 主源码已打包到
dist/index.mjs - cli脚本仅做TS转JS不打包,但转换后的
cli/runner.mjs中导入路径仍为../src/Tool.js,实际需要指向../dist/index.mjs
现有构建脚本如下:
import * as esbuild from "esbuild"; const build = async () => { try { await Promise.all([ esbuild.build({ entryPoints: ["src/index.ts"], bundle: true, outfile: "dist/index.mjs", format: "esm", target: "es2022", platform: "node", keepNames: true, external: [ "antlr4ng", "commander", "fast-printf", "stringtempalte4ts", "unicode-properties" ] }), esbuild.build({ entryPoints: ["cli/**/*.ts"], bundle: false, outdir: "cli", format: "esm", platform: "node", target: "es2022", outExtension: { ".js": ".mjs" }, }) ]); console.log("Build completed successfully"); } catch (error) { console.error("Build failed:", error); process.exit(1); } }; await build();
cli源码:
#!/usr/bin/env node import { Tool } from "../src/Tool.js"; Tool.main(process.argv.slice(2));
转换后cli脚本导入路径未修正,导致运行时找不到模块。
解决方案
方法1:规范导入路径,利用package.json解析
修改cli/runner.ts的导入路径为包根目录,让Node通过package.json配置自动解析到dist文件:
#!/usr/bin/env node // 直接导入包自身,Node会读取package.json的main/exports字段定位到dist文件 import { Tool } from '..'; Tool.main(process.argv.slice(2));
然后在package.json中配置入口:
{ "main": "./dist/index.mjs", "exports": "./dist/index.mjs", "type": "module" }
这种方式不需要修改esbuild配置,转换后的cli脚本会保留import { Tool } from '..',运行时Node会自动找到正确的dist文件。
方法2:用esbuild插件替换导入路径
如果不想修改源码导入路径,可以编写一个简单的esbuild插件,在转换cli脚本时替换目标导入路径:
修改构建脚本,为cli的build配置添加插件:
import * as esbuild from "esbuild"; import * as fs from "fs"; // 自定义替换导入路径的插件 const replaceImportPlugin = { name: 'replace-import', setup(build) { build.onLoad({ filter: /cli\/.*\.ts$/ }, async args => { let content = await fs.promises.readFile(args.path, 'utf8'); // 将../src/Tool.js替换为../dist/index.mjs content = content.replace('../src/Tool.js', '../dist/index.mjs'); return { contents: content, loader: 'ts' }; }); } }; const build = async () => { try { await Promise.all([ // 主源码构建配置不变 esbuild.build({ entryPoints: ["src/index.ts"], bundle: true, outfile: "dist/index.mjs", format: "esm", target: "es2022", platform: "node", keepNames: true, external: [ "antlr4ng", "commander", "fast-printf", "stringtempalte4ts", "unicode-properties" ] }), // cli构建配置添加插件 esbuild.build({ entryPoints: ["cli/**/*.ts"], bundle: false, outdir: "cli", format: "esm", platform: "node", target: "es2022", outExtension: { ".js": ".mjs" }, plugins: [replaceImportPlugin] }) ]); console.log("Build completed successfully"); } catch (error) { console.error("Build failed:", error); process.exit(1); } }; await build();
这个插件会在加载cli目录下的TS文件时,替换指定的导入路径,转换后的cli脚本就会使用正确的../dist/index.mjs路径。
方法3:对cli脚本开启bundle并将包源码设为external
如果允许cli脚本被bundle,但不想包含包源码,可以开启bundle并将src目录设为external,同时配置路径解析:
修改cli的build配置:
esbuild.build({ entryPoints: ["cli/**/*.ts"], bundle: true, outdir: "cli", format: "esm", platform: "node", target: "es2022", outExtension: { ".js": ".mjs" }, // 将src目录下的模块设为外部依赖 external: ['../src/*'], // 配置路径解析,将../src/Tool.js映射到../dist/index.mjs resolveExtensions: ['.ts', '.js', '.mjs'], alias: { '../src/Tool.js': '../dist/index.mjs' } })
这种方式下,esbuild会将cli脚本中的../src/Tool.js替换为../dist/index.mjs,同时不会打包包源码,符合需求。
内容的提问来源于stack exchange,提问作者Mike Lischke
相关产品推荐
相关产品推荐

