monorepo 如何解决别名问题
得益于 pnpm 软链接实现现在 monorepo 项目已经十分成熟了,不过在写项目时候还是遇到很多痛点,例如:
- 如果我有多个 packages 项目并且使用 TypeScript,那么我肯定希望是写的都是 ts,在需要调用的时候通过工具来完成这一过程的转译,不过在实际开发中可能使用了 vite 这样的工具,导致遇到问题只能通过其它方式来绕过去;
- 如果写一个后端项目,那么数据库定义的模型描述肯定希望在 web 项目也可以复用,不过在多入口中可能还需要通过
@new-house/src/xxxx.ts
来引用,这个src/xxx
也太不简洁了。
所以这篇文章重点就是解决问题 2 ,问题 1 如果有机会再单独开一个篇幅来说,在正式讲解之前需要介绍一下 exports
这个属性
exports
这个属性添加于 v12.7.0,最简单的使用方式如下
{
// package.json
"exports": "./index.js"
}
它的优先级高于 main
,除此之外更多是作为不同环境导入文件来使用,例如开发了一个 utils 的包希望它可以在 node 的 cjs 和 ems 环境下工作,那么它就可以发挥作用了。
{
"exports": {
".": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
}
}
除此之外还可以解决目录别名问题,这也是为什么介绍它的原因
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./lib/*.js": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./feature/*.js": "./feature/*.js",
"./package.json": "./package.json"
}
}
上面的 "./feature/*": "./feature/*.js",
以及 "./feature/*.js": "./feature/*.js",
等都可以通过 my-package/feature/xxx.js
来完成调用
exports 就介绍到这里,了解到它可以适配不同环境以及用于解决目录别名,下面就是项目实战。
TypeScript 下使用
为了方便,这里我已经搭建好了一个 pnpm monorepo 项目,它的文件结构如下
test
├─ node_modules
├─ package.json
├─ packages
│ ├─ utils
│ │ ├─ package.json
│ │ ├─ src
│ │ │ ├─ addition.ts
│ │ │ └─ subtraction.ts
│ │ └─ tsconfig.json
│ └─ web
│ ├─ babel.config.js
│ ├─ dist
│ │ └─ index.js
│ ├─ node_modules
│ ├─ package.json
│ ├─ src
│ │ └─ index.ts
│ ├─ tsconfig.json
│ └─ webpack.config.js
├─ pnpm-lock.yaml
├─ pnpm-workspace.yaml
└─ tsconfig.json
utils 这个项目用于给 web 项目使用,它的 package.json 内容如下
{
"name": "@test/utils",
"version": "1.0.0",
"exports": {
"./*": "./src/*.ts"
}
}
这里设置 "./*"
是提示这个为一个子目录别名,同理你也可以设置 "test/*"
表示以 test 为开头,因为 utils 这个项目都是 ts 我并不想花时间每次都 build 一遍成 js 所以只能写成 ./src/*.ts
这里提示一下,如果是 ts 项目必须这个结尾,否则会提示找不到类型文件
第二步就是在 web 项目中引用
// web/src/index.ts
import addition from '@test/utils/addition';
import subtraction from '@test/utils/subtraction';
console.log(addition(1, 1));
console.log(subtraction(1, 1));
如果 TypeScript 提示说不到模块之类的错误,那么你需要调整一下 tsconfig.json 的配置
{ "compilerOptions": { "moduleResolution": "NodeNext" // node 16也可以 } }
这样就消除了闹心的 src 目录
其它问题
TypeScript 使用别名如何定义类型文件
这个可以参考官方文档的实现
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for TypeScript resolution - must occur first!
"types": "./types/index.d.ts",
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",
// Entry-point for `require("my-package") in CJS
"require": "./commonjs/index.cjs"
}
},
// CJS fall-back for older versions of Node.js
"main": "./commonjs/index.cjs",
// Fall-back for older versions of TypeScript
"types": "./types/index.d.ts"
}
额外在 exports 中添加 types 属性,相关讨论链接 github.com/microsoft/T…
设置了 moduleResolution 导致导入其它模块必须以.js 结尾
以.js 结尾是规范的规定,如果你使用 webpack5 来打包项目可以在配置文件中添加以下内容
{
resolve: {
extensionAlias: {
".js": [".ts", ".js"],
".cjs": [".cts", ".cjs"],
".mjs": [".mts", ".mjs"],
}
// ...
}
// ...
}
相关问题链接 Support TypeScript module resolution node16
· Issue #12625 · facebook/create-react-app (github.com)
参考链接
转载自:https://juejin.cn/post/7185085487806152763