扩展名、extensionAlias 与 Node 解析
问题背景
TypeScript 库源码和运行时 JavaScript 产物之间有一个天然矛盾:
源码文件可能是:
但 ESM 规范要求源码 import 写成运行时扩展名:
构建时需要解析到 Button.ts,输出时又要保留或生成 Button.js。这就是 Rslib 里 extensionAlias、autoExtension、bundleless redirect 共同解决的问题。
extensionAlias 做什么
Rslib 内置配置中有:
它解决的是“源码解析”问题。用户写:
构建时可以解析到:
这符合 TypeScript + Node ESM 的推荐写法:源码里写运行时扩展名,但开发时仍解析 TS 源文件。
autoExtension 做什么
autoExtension 解决的是“输出扩展名和 import 扩展名”问题。它会结合:
- format。
- package.json
type。 - 用户 output filename。
推导最终 JS extension 和 dts extension。
例如:
- ESM 在某些 package type 下可输出
.js。 - ESM 也可能需要
.mjs。 - CJS 可能需要
.cjs。 - dts 可能需要
.d.mts或.d.cts。
为什么 bundleless 必须改 import 扩展名
Bundleless 输出保留文件间 import。如果源码是:
输出 ESM 如果仍是:
Node ESM 不会自动解析 ./Button.js。所以 Rslib 会补成:
如果输出扩展名是 .mjs,就补 .mjs。
目录 import
源码可能写:
而实际是:
当 redirect.js.path 关闭但 redirect.js.extension 开启时,Rslib 仍需要判断目录 import,补出:
否则只补成 ./Button.js 就错了。
用户自定义 filename
用户可以配置:
这种情况下,Rslib 会从用户 filename 推导 final JS extension。bundleless redirect 也要跟着用 .jsx。
测试 preserve-jsx 里就覆盖了 .jsx 输出。
dts extension
如果用户开启 dts.autoExtension,Rslib 会把 dtsExtension 传给 plugin-dts。这样:
或:
可以保持模块语义一致。
关掉 autoExtension 的风险
用户可以关掉 autoExtension,但风险是:
- Node ESM import 可能缺扩展名。
- CJS/ESM 在 package type 下可能被 Node 误判。
- dts extension 可能和 JS 产物不匹配。
- bundleless 输出 import 需要用户自己保证可解析。
因此 core 默认开启 autoExtension。
相关测试
应关注:
tests/integration/auto-extensiontests/integration/extension-aliastests/integration/redirecttests/integration/preserve-jsxtests/integration/format
扩展名相关问题通常表现为运行时解析失败,而不是构建失败。