在开发基于 Typescript 的 NodeJS 项目时,我们通常会 tsconfig.json
中配置 paths
字段来设置路径别名(文档):
1 | { |
但这里很容让人产生一个错误认知,很多人会意为这里配置的路径别名与 webpack 中配置的 alias
是一样的,我们配置完 paths
后就去写下如下代码:
1 | import {xxx} from "@/xxx" |
然后使用 tsc 进行编译或者使用 ts-node 运行代码,就必定发生如下报错:
1 | Error: Cannot find module '@/xxx' |
这里我们需要认识到如下两点:
- 在
tsconfig.json
中配置的路径别名,只触发 vscode 的包索引,得以让你使用代码提示来找文件路径; - tsc、ts-node 在编译 ts 文件时,不会通过
tsconfig.json
中配置的paths
来进行包索引,你可以查看编译后的 js 文件,文件路径仍保持编码时的形态,并没有得到转换,因此在 nodejs 运行时必定会发生无法查找到模块路径的报错。
为了解决上述的问题,可以使用如下解决方案:
1. module-alias
module-alias 是一个在运行时对模块路径进行转换的插件,你可以通过将路径别名写入到 package.json
或者是入口文件的顶部,即可让你的代码在运行时使用路径别名。
示例:
1 | // 入口文件 |
使用 module-alias
的好处是让代码在运行时进行路径解析,意为着你不需要考虑开发时和编译后的代码路径转换问题,但是这样会导致你不仅需要在 tsconfig.json
中写入路径别名,也需要在 module-alias
使用时声明路径别名。
同时运行时解析路径意味着路径解析是动态的,效率上必定会有所损失。
2. tsconfig-paths
tsconfig-paths 是比 module-alias
更好的一个替代模块,它的原理跟 modules-alias
是相似的,但是它会自动读取 tsconfig.json/jsconfig.json
中配置的路径别名,意味着你不需要二次配置,它有两种使用方式,一种是在代码入口中直接引入该包的 register:
1 | // 保证 register 先被加载 |
另外一种则是通过官方推荐的在 node/ts-node
运行指令中使用 -r
参数 来引入 register:
1 | # node |
如果你使用了 nodemon
作为开发时监听代码变更的工具,虽然 nodemon 会自动根据当前环境选择调用 node 还是 ts-node 作为代码的运行时环境,但并不会去调用 tsconfig-paths/register
,因此我们可以编写一个 nodemon.json
文件来改写 node 执行代码时的行为:
1 | { |
3. 使用前端编译工具解析模块别名
上述的两种方案都是让代码在运行时解析文件路径,因此在运行时必定会有一定的性能损耗,这个性能损耗在开发环境下我们可以无视掉,但是在正式环境下我们还是希望可以直接生成一个可以访问的静态路径,避免路径转换带来一定的损耗。
要达到这个目的我们不得不借助前端编译工具来实现路径转换,以 rollup 为例:
1 | import alias from "@rollup/plugin-alias" |
这样编译后的代码就实实在在的转化为了一个可以被查找到的 相对路径,比如:
1 | // 编译前 |
4. 总结
tsconfig.json
声明的路径别名并不会被 tsc 识别并进行转换,仅供 vscode 的路径提示可以识别;- 在开发环境下可以使用
tsconfig-paths
来做路径转换; - 在最终的编译阶段,最好还是使用编译工具来进行路径转换。