前言
本来想着用 Node 写点东西玩儿,写着写着发现没有 TypeScript 的类型提示真的好难受,于是马不停蹄地安装 TypeScript 一系列工具包:
bash
$ npm install typescript ts-node @types/node --save-dev
再通过手动创建 tsconfig.json 文件,并手动指定相关选项:
json
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"baseUrl": "src",
"outDir": "dist",
"sourceMap": true,
"noImplicitAny": true
},
"include": ["src/**/*"]
}
使用 nodemon 的热重载,避免手动重启:
json
// nodemon.json
{
"watch": ["src"],
"ext": ".ts .js",
"exec": "ts-node ./src/index.ts"
}
配置 scripts 命令:
json
{
// 省略其他部分
"main": "index.ts",
"type": "module",
"scripts": {
"start:dev": "cross-env NODE_ENV=dev nodemon"
}
}
然而,在启动之后报错:
css
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
本着 Bug 不可怕原则,就开始着手解决了,然而没想到这个错误竟然耗费了我整个下午...
即使是去搜索网上的解决方案,也依然没有解决"我"的问题。所以将解决该错误的方式总结成一篇文章,方便下次回顾👀
ts-node
可以看到究其原因是 nodemon 在执行命令时出错了,根本原因还是在 ts-node 没有执行成功。那么,什么是 ts-node 呢?
简而言之,ts-node 是 Node 环境下的 TypeScript 引擎和 REPL,它将 TypeScript 编译为 JavaScript ,并且可以在 Node 环境中执行 TypeScript ,无需任何预编译操作。由于在内部挂载了 Node 模块加载的 API ,所以可以和其他 NodeJS 工具无缝地使用。
更多内容可以看看官方文档。
解决方案
esm
可以在使用 ts-node 时尝试添加 --esm 标签或者在 tsconfig.json 文件中开启 esm 。
--esm 标签
在原来的 nodemon.json 文件中添加上 esm 标签:

运行启动命令后:

可以看到能够正常运行了。
在配置文件中开启 esm
我们将原来 nodemon.json 文件中的 --esm 标签删掉:

转向 tsconfig.json 配置文件,并在该文件中开启 esm:

运行启动命令后:

可以看到依然能够正常运行,其实以上两种方式是等价的,都是同一种解决办法,只不过是两种形式而已。
esm 表示启用 ES Module loader,通过这个 loader 就可以将 import 等 ES6 语法与 ts-node 结合使用了。
开启 ES Module loader ,还有两种办法,其实就是通过指定 loader :
使用 ts-node-esm
我们通过以下命令来运行:
bash
$ npx ts-node-esm src/index.ts
将原来在配置文件中开启 esm 进行删除,并保持 nodemon.json 和 tsconfig.json 和之前一样:


运行上方的命令:

可以看到能够正常运行,使用 npx 命令时,如果本地没有安装指定的包,则 npx 会自动帮你安装并执行该包的命令。具体的查找规则:
- 当前目录下的
node_modules/.bin目录 - 全局下的
node_modules/.bin目录 - 以上两个地方都没找到,
npx就会下载该包,并将其安装在临时目录下,然后执行相应的命令。
这个临时目录在哪儿?
它通常是系统的临时目录,不同的系统有不同的临时目录:
- 在
Linux上,临时目录是/tmp - 在
Mac上,临时目录是/var/folders - 在
Windows上,临时目录是%TEMP%或%TMP%
由于我使用的是 Mac ,所以去看看临时目录里有啥,当然我们并不知道临时目录在哪儿,需要一条指令来查看临时目录的位置:
bash
$ echo $TMPDIR # 适用于 Mac/Linux
$ echo %TEMP% # 适用于 Windows
执行以上命令:

可以看到临时目录已经出来了,我们进入该目录看看有啥:

可以看到有非常多文件,继续往下滑就能看到熟悉的身影:

可以看到其中有非常多 yarn 开头的文件,这些文件就是通过 npx 命令下载的包的临时目录了,进入其中一个临时目录看看:

可以看到有 node 和 yarn 两个关键词,它们表示通过 npx yarn 命令下载的 yarn 包,也就是说 ts-node-esm 这个包是通过 yarn 这个包管理工具下载的。
除了使用 ts-node-esm 这个包,还可以使用 --loader 标签。
--loader 标签

可以看到 --loader 标签是通过 node 运行的,并且指定该 loader 为 ts-node/esm 即可。
该方式下,还可以使用 Node 环境变量注入指定的 loader :
bash
$ NODE_OPTIONS="--loader ts-node/esm" node src/index.ts
看看输出:

可以看到能够成功运行,当然该指令目前只能在 Mac/Linux 系统下或 Git bash 环境运行,如果你想在 Windows 平台上及 CMD 或 PowerShell 环境运行时,可以安装 cross-env 包来运行。
其实使用 ts-node-esm 和 --loader 也是等价的,只是 ts-node-esm 相当于是显式地使用 --loader 标签。
综上所述,对于开启 esm ,其实有四种解决办法:
--esm标签- 在
tsconfig.json文件中开启esm: true - 使用
ts-node-esm工具包 - 指定
node loader为ts-node/esm
删除 type: module
当 type 属性设置为 module 时,项目中的所有 .js 文件都被视为 ES Modules 。当使用 ts-node 时这会导致问题,因为 ES Modules 没有完全集成。如果删除掉 type 字段或将其设置为 commonjs ,则所有的 .js 文件都将被视为 commonjs。
将 package.json 文件中的 type 字段删除:

运行启动命令:

可以看到能够运行成功。当然这种解决方式不太合理,尽量不要使用这种方式。
tsc 和 node 替代
ts-node 实际上就是结合了 tsc 和 node ,如果 ts-node 使用上述方案仍然不能够运行,可以使用 tsc + node 进行替代:
通过以下命令进行替代:
bash
$ npx tsc --outDir dist && node dist/index.js
看看输出:

可以看到能够运行成功。
如果以上方案都不能解决问题,可以再仔细检查检查配置,或者干脆就不用 TypeScript 🤣,希望这篇文章能够帮助你。