问题由来
我正在开发的 cli 工具 wcli 是用node
+typescript
编写的,在编写的过程中不免需要引用到大量的模块。
大量的import utils from '../../../../../utils'
太麻烦也太恶心了,于是开始了 ts 的路径别名踩坑之旅。
ts 的路径别名设置
ts 的路径别名设置在社区中已经十分成熟了,官网中也有相应的文档描述。接下来的内容将直接引用官网的描述以及设置。
ts 官网的路径映射配置
有时模块不是直接放在baseUrl
下面。 比如,充分 "jquery"
模块地导入,在运行时可能被解释为node_modules/jquery/dist/jquery.slim.min.js
。 加载器使用映射配置来将模块名映射到运行时的文件,查看 RequireJs documentation和SystemJS documentation
。
TypeScript
编译器通过使用 tsconfig.json 文件里的"paths"来支持这样的声明映射。 下面是一个如何指定 jquery
的paths
的例子。
{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // 此处映射是相对于"baseUrl"
}
}
}
请注意paths
是相对于"baseUrl"
进行解析。 如果 baseUrl
被设置成了除"."外的其它值,比如 tsconfig.json 所在的目录,那么映射必须要做相应的改变。 如果你在上例中设置了 baseUrl: ./src
,那么 jquery 应该映射到../node_modules/jquery/dist/jquery
。
课代表总结
- baseUrl: 用来设置下面 path 路径的根路径。
- path: 用来设置相对应的路径映射,数组中的路径相对应 baseUrl 的路径。
"baseUrl": ".",
"paths": {
"@srcTypes/*": ["src/types/*"],
"@constants/*": ["src/constants/*"],
"@commands/*": ["src/commands/*"],
"@utils/*": ["src/utils/*"]
}
配置完即可在项目中使用。
import XXX from "@utils/getPluginFile";
import { xx } from "@utils/checktype";
import { xx } from "@utils/createContext";
tsc 的坑
路径映射是做好了,但是在执行 tsc 打包完之后的代码发现报错了:用 tsc 编译的后,映射的路径不会处理,将导致编译后的代码找不到模块。
比如上面的 import XXX from '@utils/getPluginFile'
,如果你使用 commonjs 模式编译后应该是 const XXX = require('@utils/getPluginFile')
,结果就是找不到这个模块。
解决办法
根据是否使用 webpack 打包,请自行选择
- 如果项目是通过 webpack 进行打包的,那么可以把路径的替换交给 webpack 的 alias 处理即可。
module.exports = {
//...
resolve: {
alias: {
@utils: path.resolve(__dirname, 'src/utils/')
}
}
};
- 问题是我的 wcli 不是用 webpack 打包的,纯粹就是使用 tsc 编译成 js 在 node 上执行的那怎么办呢?后面发现社区上提供了一个库-
module-alias
。在配置完 tsconfig 的基础上只需要通过简单的两步即可实现。 在package.json
配置跟 webpack alias 的路径指向然后在入口文件调用引用方法即可。
// Aliases
"_moduleAliases": {
"@root" : ".", // Application's root
"@deep" : "src/some/very/deep/directory/or/file",
"@my_module" : "lib/some-file.js",
"something" : "src/foo", // Or without @. Actually, it could be any string
}
// 在入口文件最顶端调用即可
require('module-alias/register')
一些另外的用法可以去官网查看即可,这里是跳转连接。
eslint 的坑
根据上面的内容,tsc 是好使了打包出来的代码也能正常运行。不过有一个新的坑出来了:eslint 并不能识别路径别名,虽然能够正确跳转以及引用。难受啊!马飞!。
最简单最暴力的方式自然是直接去掉这个插件,或者关闭相关 ESLint rules
,但 eslint-plugin-import
30+ rules 集合 JS 社区 ES6 多年最佳实践,关闭这个规则实乃下下策。最终在一位大佬的博客下找到了一个完美的 eslint 插件解决这个问题-eslint-import-resolver-typescript
。
eslint-import-resolver-typescript
eslint-import-resolver-typescript光从名字就可以看出和这个问题极为相关。从项目 README 可以发现,这个 lib 可以在 TypeScript 项目使 eslint-plugin-import 找到正确的 .ts 和 .tsx 文件,也能识别 tsconfig.json 的 path 配置(路径别名 2),甚至 monorepo 这类一个 git 仓库多个项目的工程也支持。
用法也很简单在 eslint 的"import/resolver":
指向当前配置了 path 的 tsconfig 的路径即可,eslint 就会自动识别就不会报错了。
{
"plugins": ["import"],
"rules": {
"import/no-unresolved": "error"
},
"settings": {
"import/parsers": {
// 使用 TypeScript parser
"@typescript-eslint/parser": [".ts", ".tsx"]
},
"import/resolver": {
// 默认使用根目录 tsconfig.json
"typescript": {
// 从 <roo/>@types 读取类型定义
"alwaysTryTypes": true,
},
// 使用指定路径 tsconfig.json, <root>/path/to/folder/tsconfig.json
"typescript": {
"directory": "./path/to/folder"
},
// monorepos 这类多 tsconfig.json
// 可以用 glob 这类匹配模式
"typescript": {
"directory": "./packages/*/tsconfig.json"
},
// 或者数组
"typescript": {
"directory": [
"./packages/module-a/tsconfig.json",
"./packages/module-b/tsconfig.json"
]
},
// 也可以混合使用
"typescript": {
"directory": [
"./packages/*/tsconfig.json",
"./other-packages/*/tsconfig.json"
]
}
}
}
}
最后
贴一下我的 eslintrc 配置,可以作为参考:
module.exports = {
'parser': '@typescript-eslint/parser', //定义ESLint的解析器
'extends': ['airbnb-base', 'plugin:@typescript-eslint/recommended'],//定义文件继承的子规范
'plugins': ['@typescript-eslint'],//定义了该eslint文件所依赖的插件
'env': { //指定代码的运行环境
'browser': false,
'node': true
}
'settings': {
//解决路径引用ts文件报错的问题
'import/resolver': {
'node': {
'extensions': ['.js', '.jsx', '.ts', '.tsx']
},
// 解决tsconfig下的path别名导致eslint插件无法解决的bug
'typescript': {
'alwaysTryTypes': true
}
}
},
}
参考文献
ESLint 检查 TypeScript 时报 “Unable to resolve path to module ‘xxx’” 错误