发生背景
书接上回,在 CommonJS 中使用 ES Module 语法,ts-node 会报错 ERR_UNKNOWN_FILE_EXTENSION
时隔 9 个月,报错再次发生:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for ...\scripts\upload.ts
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
at defaultLoad (node:internal/modules/esm/load:141:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async Hooks.load (node:internal/modules/esm/hooks:448:20)
at async MessagePort.handleMessage (node:internal/modules/esm/worker:196:18) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
相较于上次,这次的工具更新些:
- Node.js 20.10.0
- ts-node 10.9.2
- TypeScript 5.3.3
再三确认:
- 要运行的文件
scripts/upload.ts
存在 package.json
里有写"type": "module"
package.json
里有写"ts-node": { "esm": true }
(换用ts-node-esm
也会报错)
搞了一个星期,终于发现了问题的根源。
解决方案
(省流:最好别在 ts-node 一棵树上吊死,非要吊上去的话稍微调一下也不会死)
首先看 tsconfig.json
:
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ESNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"ts-node": {
"esm": true
}
}
发现第一个原因:tsconfig.json
的 include
中没有包含要运行的文件(scripts/upload.ts
)。
解决方法:
@@ -1,7 +1,7 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
- "include": ["src/**/*.ts"],
+ "include": ["src/**/*.ts", "scripts/**/*.ts"],
"ts-node": {
"esm": true
}
再试试,发现还报一样的错。
GitHub 上有老哥遇到同样的问题,他的建议是运行 node --loader ts-node/esm <文件路径>
而不是 ts-node <文件路径>
。
在 Node.js v20.10.0 中 --loader
是个即将废弃的实验性功能,但无所谓,能用就行(
改用 node --loader ts-node/esm scripts/upload.ts
,好歹报了个新的错:
node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
^
[Object: null prototype] {
[Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]]
}
回到 scripts/upload.ts
中,发现 VSCode 提示有一处类型错误:
Parameter 'e' implicitly has an 'any' type. ts(7006)
改改代码,解决掉这个类型错误,再试试运行,成功了!
发生原因
ts-node 10.9.2 的 --esm
选项在 Node.js 19+ 中似乎坏了,相应的 issue 在 GitHub 上从 2023/4/20 一直挂到现在(2024/2/2),至今足足积了 207 个👍还没 member 或 contributor 回复……
由于 --loader
即将废弃,最佳的做法是:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));' <文件路径>
或者把 --import
后面的一个参数抽出来单独写个脚本。
笔者在 ts-node 的源码里挖了半个小时,没能找出报错的根本原因,ts-node 源代码读不了一点(逃
issue 下有老哥建议用 tsx 或 tsimp 或 swc-node 代替 ts-node。
笔者现在用 tsx,感觉良好,比 ts-node 快很多,而且本文的报错在 tsx 中根本不会出现。