使用 yarn3 PnP + workspace + typescript 从零开始搭建一个 monorepo
先看下项目的整体结构:
使用 yarn 初始化项目
- 先执行 yarn set version stable 使用稳定版本, 当前是 v3.4.1。
- 执行 yarn init 初始化项目
- 执行 mkdir packages 新建 packages 文件夹
- 修改 package.json, 声明 workspace
"workspaces": [
"packages/*"
]
yarn v2+ 默认开启 PnP 模式, 因此无需修改 .yarnrc.yml 文件
初始化子项目
1. 执行以下命令
yarn create react-app packages/app1 --template typescript
yarn create react-app packages/app2 --template typescript
按照 monorepo 的惯例,子项目的名称最好命名为@<主项目名称>/<子项目名称>
。分别进入 app1 和 app2 的package.json
中修改 name :
@yarn3-demo/app1
@yarn3-demo/app2
然后删除这两个子项目中的 node_modules文件夹。
create-react-app 在初始化项目的过程中就会安装依赖到 .yarn/cache 中。
接着,手动创建 common 子项目备用
- 进入 packages 文件夹,执行 mkdir common
- 进入 common 文件夹,按照下面目录新建文件
└── common
├── README.md
├── components
│ └── test.ts
├── index.d.ts
├── index.ts
└── package.json
- 修改 common 子项目名称为 @yarn3-demo/common
在 test.ts 中编写一个测试函数
export function add(x: number, y: number) {
return x + y;
}
在 index.ts 中导出
export { add } from './components/test';
修改 package.json 中的 main 字段:
"main": "index.ts"
2.tsconfig.json 共享和 ts 支持
tsconfig.json 共享
把子项目中的 tsconfig.json copy 一份到顶层并删除其中的 include
属性
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
}
}
修改子项目的 tsconfig.json
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
ts 支持
这个时候 ts 还找不到依赖包的定义。这是因为 .yarn/cache 下都是 .zip 文件,ts 解析器无法直接读取 .d.ts 文件。
typescript 官方目前也不支持读取 .zip 下的 .d.ts,尽管 yarn 团队已经为 PnP 提了 PR。 https://link.juejin.cn/?target=https%3A%2F%2Fyarnpkg.com%2Fgetting-started%2Fqa%23why-is-typescript-patched-even-if-i-dont-use-plugnplay
下面按照官方给出的步骤一步步操作:https://link.juejin.cn/?target=https%3A%2F%2Fyarnpkg.com%2Fgetting-started%2Feditor-sdks
- 在 vscode 中安装 ZipFS
- 在根目录下执行
yarn dlx @yarnpkg/sdks vscode
- 这个时候 vscode 会弹窗提示选择一个 typescirpt 版本,我们选择 Use Workspace Version 即可。
这个时候就会发现 ts 已经可以找到依赖的定义了😀。
共享配置文件
配置文件包括 .prettierrc .eslintrc.js .editorconfig
等, 这里我们只着重说项目构建相关配置
在根目录执行
yarn workspace @yarn3-demo/app2 run eject
这个是 create-react-app
给我们提供的一个用来自定义构建的命令。执行后 CRA 项目中的 config
文件夹和 scripts 文件夹会弹出来, 这两个文件夹下的文件的依赖会放在项目的package.json
中。我们需要做的就是:
- 把 config 文件夹和 scripts 文件夹移动到根目录下
- 在 config/paths.js 中新增 packages 路径
packages: path.resolve(__dirname, '../packages/')
- 在 config/webpack.config.js 中修改 babel-loader 配置
include:paths.appSrc -> include: [paths.appSrc, paths.packages]
- 修改 package.json 中的
scripts
"start": "yarn node ../../scripts/start.js",
"build": "yarn node ../../scripts/build.js",
注意这里要使用 yarn node
https://link.juejin.cn/?target=https%3A%2F%2Fyarnpkg.com%2Fgetting-started%2Fmigration%2F%23call-your-scripts-through-yarn-node-rather-than-node
依赖管理
1. 给子项目安装第三方依赖
拿 lodash 举例, 执行命令
yarn workspace @yarn3-demo/app2 add lodash
删除依赖
yarn workspace @yarn3-demo/app2 remove lodash
另外,我们一般安装第三方依赖的时候会把其对应的 @types/xxx 也安装,比如 ```bash yarn add lodash @types/lodash
比较麻烦也有可能会忘记安装 types 文件,yarn 提供了一个插件可以自动为我们安装 types 文件,执行
yarn plugin import typescript
然后重新安装 lodash
这样 @types/lodash 就会自动被安装在 devDependencies
中
2. 子项目安装其他作为本地依赖的子项目
现在我们要在子项目 app2 中引用 common,在根目录下执行
yarn workspace @yarn3-demo/app2 add @yarn3-demo/common
查看 app2 的 package.json 文件会发现 dependencies
中多了
"@yarn3-demo/common": "workspace:^"
测试一下,在 app2/src/index.tsx 中
import { add } from '@yarn3-demo/common';
console.log(add(1, 2));
几个需要注意的点:
- 子项目不能使用父级依赖。
- 无论是父级还是子项目都只能使用自己的 package.json 中显示声明的依赖,这是因为 PnP 默认为严格模式,不建议更改。
- 如果是手动修改了 package.json 一定要记得执行 yarn isntall 重新生成 .pnp.cjs,目的是重新构建依赖树让 node 可以定位到我们的依赖。
启动子项目
以上所有准备已经完成,启动子项目试试吧~
执行命令
yarn workspace @yarn3-demo/app2 start
success😀
版本控制
现在我们要发布 app2 了,如果只是修复了一个小 bug , 那我们可以
执行命令, 安装 version 插件
yarn plugin import version
修改版本
yarn workspace @yarn3-demo/app2 version patch
查看 package.json 会发现 app2 的版本从 0.1.0 变成了 0.1.1。
更多可以查看 yarn version 命令
打包
执行命令
yarn workspace @yarn3-demo/app2 run build
总结
PnP
yarn v2 版本起默认开启了 PnP 的功能,这个功能开启后项目将不再存在 node_modules 文件夹,所有的依赖都会被压缩成一个 .zip 文件存放在 .yarn/cache 中。
零安装
由于压缩后的包体积很小,而且包的数量不会很多,我们可以直接把 .yarn/cache 上传到 git 仓库, 这样一来的好处:
- 更好的开发体验。你每次使用 git clone git pull 等命令更新完你的代码后无需使用 yarn install 进行依赖的安装,这样可以避免一些问题的出现,例如别人更新了某个依赖的版本后,如果你没有进行对应的更新的话,你的代码可能会报错。
- 代码 review 的时候可以更清楚哪些依赖发生了改变。
- 更快,更简单,更稳定的 CI 部署。由于每次部署代码的时候,yarn install 占用的时间都是一个大头,去掉这个步骤后部署速度将会大大提升。
- 不会存在本地运行没问题,发布线上环境的时候挂掉了的问题。
- 不用你在 CI 文件里面进行一些安装依赖的配置。
零安装好处毋庸置疑,但显得比较激进,.yarn/cache 对于开发者更像一个黑盒:
-
monorepo 下无论是子项目依赖还是父级依赖都安装在 .yarn/cache,没有一个清晰的结构
-
因为 .zip 格式,ts 支持不友好,需要额外工作量(上面已经提到)
-
同样因为 .zip 格式,想要调试某个依赖的话需要额外工作量(需要 yarn unplug)
-
实际测试中 vscode 的 ts 服务有时候会挂掉(可能是因为根目录没有安装 typescript 或者版本过低)
优劣对比之下,通过 yarn3 PnP + workspace + typescript 来搭建 monorepo 还是非常值得一试的~
后续还会尝试一下 pnpm + workspace + typescript 来搭建 monorepo,敬请期待~
本文产出 git 仓库地址:github.com/ohguaiguai/… 如想尝试可直接 git clone, 无需 yarn install 体验下零安装👏👏👏
git clone 之后,启动项目会报错:
Error: Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).
Missing package: open@npm:8.4.0
Expected package location: /Users/zhangxing/Desktop/yarn3-demo/.yarn/unplugged/open-npm-8.4.0-df63cfe537/node_modules/open/
这是因为部分包是默认 unplugged 的, 比如上面的 open@npm:8.4.0, 可能还需要 yarn install 一次。感谢大佬@洛冰河指正~
按照官网说的配置 enableScripts: false
之后,还是无法在 git clone 之后直接运行。看起来如果使用了默认为 unplugged 的包必须要重新 yarn install 一次。
_转载 _