用 pnpm workspace 管理 TypeScript 多包项目依赖
之前项目里包一多,每个包单独 npm install,改个公共包的版本,得挨个改 package.json,然后挨个 install,烦得很。后来换成 pnpm workspace,一开始没搞明白 workspace:*,改完 shared 的代码,web 里死活不更新,查了半天才发现依赖版本号写死了。下面是我现在用的方式,不算什么最佳实践,但跑起来挺省心。
先看清楚目录结构
我一般把项目放成这样:
my-monorepo/
package.json
pnpm-workspace.yaml
tsconfig.base.json
packages/
shared/
package.json
tsconfig.json
src/
web/
package.json
tsconfig.json
src/
server/
package.json
tsconfig.json
src/
packages 下每个目录就是一个独立的包,有自己的 package.json 和 tsconfig.json。根目录只放公共配置和脚本,不放业务代码。
根目录就两个关键文件
第一个是 pnpm-workspace.yaml,内容很简单:
packages:
- 'packages/*'
这行就是告诉 pnpm,packages 下面每个文件夹都算 workspace 成员。你要是还有 apps 目录,就再加一行 - 'apps/*'。
第二个是根目录的 package.json:
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "pnpm -r build",
"dev": "pnpm --parallel -r dev",
"typecheck": "pnpm -r typecheck"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}
private 一定要设成 true,别手贱去掉,不然 pnpm 可能会把根包当发布包处理。公共的开发依赖,比如 typescript、eslint、prettier,统一装在根上,用 pnpm add -w -D typescript,-w 就是 workspace root 的意思。别在根上装业务依赖,不然子包引用起来会乱。
包之间依赖用 workspace:*
比如 web 要用 shared,就在 packages/web/package.json 里这么写:
{
"name": "@my/web",
"dependencies": {
"@my/shared": "workspace:*"
}
}
workspace:* 会直接链接到本地包,你改 shared 的代码,web 里立刻就能看到。如果写具体版本号,比如 "^1.0.0",pnpm 可能去 registry 拉,本地改动不生效。这个坑我踩过,改了半天 shared,web 里一点反应没有,后来把版本号换成 workspace:* 才正常。
给某个包加依赖,别 cd 到子包再 pnpm add,统一在根目录用 --filter:
pnpm add axios --filter @my/web
pnpm add -D vitest --filter @my/shared
--filter 后面写包名全称,别只写 web,不然匹配不到。装完看一眼 pnpm-lock.yaml,只有一个,提交它就够了。
TypeScript 配置别各写各的
多包项目类型检查容易出问题,我一般抽一个根 tsconfig.base.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"declaration": true,
"composite": true,
"skipLibCheck": true
}
}
每个子包的 tsconfig.json 继承它:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
如果不想每个包单独 build,可以在根目录加一个 tsconfig.json,用 project references:
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/web" },
{ "path": "packages/server" }
]
}
然后 tsc -b 一次性构建,shared 会先于 web 构建。注意 composite 和 declaration 得开着,不然 references 会报错。
常用命令和几个坑
pnpm -r build 会按拓扑顺序递归执行所有包的 build,shared 先跑,web 后跑。想并行跑开发服务,用 pnpm --parallel -r dev。只想跑某个包,就 pnpm --filter @my/web dev。
几个实际遇到的坑:pnpm 的 node_modules 是符号链接结构,有些老工具不认,比如旧版 webpack 要配 resolve.symlinks,Vite 一般没事。子包里的 .bin 实际在根 node_modules/.bin,脚本里直接写命令就行。还有,如果某个包要独立部署,记得用 pnpm deploy 把 workspace 链接打包成实际依赖,别直接把带符号链接的 node_modules 扔上去。
大概就这些。先把根目录、workspace:* 和 --filter 弄对,后面基本就顺了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。