动机

  • 不再同时使用两个 monorepo 管理工具,或者说将 monorepo 功能整合到 yarn 里(新的依赖管理工具 npm7/pnpm/rush 都是这样干的),专注于增强 yarn(yarn.build 是一个不错的例子)
  • 使用 yarn2 逐渐发展的生态(yarn1 基本没什么更新了)
  • lerna 的高级功能不太好用,主要是 lerna run 不支持缓存导致每次重新构建会很烦 -- 考虑使用 ultra 支持,但 yarn2 似乎有插件可以支持,参考: https://github.com/yarnpkg/berry/issues/2374open in new window
  • yarn2 不需要使用 lerna clean -y && lerna bootstrap 来将动态构建的 cli 写入到 node_modules/.bin,它查找 cli 的方式发生了变化(完全动态化),参考 https://yarnpkg.com/getting-started/migration#call-binaries-using-yarn-run-rather-than-node_modulesbinopen in new window
    • 该优化大约能将每个 cli 的构建时间从 40s 降低至 10s 内,之前大部分时间都是写入 node_modules/.bin

如何使用类似于 lerna run/exec 之类的命令

  1. 使用 workspaces 插件 yarn plugin import @yarnpkg/plugin-workspace-tools
  2. 使用 yarn workspaces foreach 在所有模块执行命令

下面是在所有模块中按照依赖顺序尽可能地并行构建包

yarn workspaces foreach -p --topological-dev run build
# 或删除所有 dist
yarn workspaces foreach -p exec rimraf dist
# 如果希望删除 node_modules,则需要使用 yarn dlx
yarn workspaces foreach -p exec yarn dlx rimraf node_modules
1
2
3
4
5

如何像 lerna publish 更新所有模块的版本

升级全部模块目前可以使用 yarn workspaces foreach 模拟 lerna publish,但对于独立模式则是另外一套完全不同的模式了。

yarn workspaces foreach exec yarn version patch
1

缺点是无法按照是否修改决定是否发布新版本。。。

发布所有包

yarn npm publish 仍有 bug,所以只能直接 npm publish

yarn workspaces foreach -A --no-private exec npm publish
1

感觉还是需要实现缓存的功能,只是将缓存文件加到 git 而已。考虑到不是所有命令都需要,也许可以使用缓存目录。

如果包含 cli 子模块怎么办?

打包完成之后 yarn 安装即可,yarn 会负责将命令写入到依赖的其他子模块的 node_modules/.bin/ 中。

编写插件

插件教程文档open in new window

monorepo 构建.drawio.svg

问题

  • 不能使用 lerna 那种拼接脚本的方式,即将 lerna run --include-dependencies --stream 放在 stream 脚本,然后使用 yarn stream <cmd> 拼接命令的形式。
  • 不确定 yarn 是否包含 lerna publish 那种自动检测和批量发布的命令 -- 支持不太好用
  • 不确定 yarn 是否有类似于 rush/nx 的构建缓存功能 -- 没有原生支持,两个插件也都有各自的问题
  • 概念太多实在太麻烦了,各种奇怪的问题
    • yarn npm publish 会说权限错误,但实际上 yarn npm login 已经成功了,文档上说可以配置 npmAuthToken,但这是不合理的(将 token 加到 git 管理中)
    • yarn workspaces foreach exec yarn jest --all 会报一个错误
  • 对于 yarn2 pnp,目前 webstorm 仍然只有非常基本的支持,包括 prettier/jest 都存在问题,参考:https://youtrack.jetbrains.com/issue/WEB-35034open in new window
  • yarn 2 的社区接受度似乎极低,github 上依赖它的库不超过 100 个,参考:https://github.com/yarnpkg/berry/network/dependents?package_id=UGFja2FnZS03MDE5NDg3MjU%3Dopen in new window -- 实际上这是错的,由于 yarn2 不在 package.json 中声明而已,目前 storybook 等流行库也升级了 yarn2,但它们也混用 lerna 和 nx。。。
  • 无法使用 patch-package,但可以使用 yarn patch 命令。。。

其他优点

  • 原生支持 workspaces
  • 有插件 api,可以为自定义需求编写插件
  • cli 命令执行不再强绑定到 node_modules/.bin
  • 重新安装依赖时时不需要停止 vite dev(目前无法安装成功)

编写插件之后应该怎么在本地测试?

  1. 单元测试

    const dir = npath.toPortablePath(path.resolve(""));
    const configuration = await Configuration.create(dir, dir);
    const project = (await Project.find(configuration, dir)).project;
    
    1
    2
    3
  2. yarn plugin import <>

yarn workspace 有办法包含一个不在 workspace 管理的子目录么?

例如项目可能有一个 website 目录用以存放网站的文档,而又不希望将之放到 yarn workspace 管理(有可能是不同的技术栈,例如 vuepress)。

workspace 字段中排除,然后在目录中添加空的 yarn.lock 文件

npmrc => yarnrc.yml

yarn2 在 monorepo 中根模块无法直接 import 子模块的 yarn 插件

复现仓库open in new window

复现步骤

  1. yarn && yarn setup 安装依赖及初始化
  2. cd libs/yarn-plugin-changed && yarn build 打包插件
  3. cd ../.. && yarn plugin import libs/yarn-plugin-changed/bundles/\@yarnpkg/plugin-changed.js 回到根目录安装插件
  4. 得到错误
$ yarn plugin import libs/yarn-plugin-changed/bundles/\@yarnpkg/plugin-changed.js
➤ YN0001: Error: Invalid locator (@yarnpkg/plugin-libs/yarn-plugin-changed/bundles/@yarnpkg/plugin-changed.js)
    at Object.hA (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:242:12395)
    at C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:387:1702
    at async Function.start (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:275:2287)
    at async Rp.execute (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:387:1110)
    at async Rp.validateAndExecute (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:197:620)
    at async ts.run (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:211:1846)
    at async ts.runExit (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:211:2013)
    at async i (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:310:12327)
    at async r (C:\Users\rxliuli\Code\Pkg\liuli-tools\.yarn\releases\yarn-berry.cjs:310:10567)YN0000: Failed with errors in 0s 133ms
1
2
3
4
5
6
7
8
9
10
11
12

答案

必须使用 . 开头的相对路径

github issueopen in new window

yarn 插件打包后在 bundles/@yarnpkg 下而非我们的组织名下面

同样是在编写 yarn 插件时遇到的错误,在我们使用 yarn plugin import 安装插件之后,才发现安装的目录是 .yarn/plugins/@yarnpkg/plugin-changed.cjs,这似乎有些不对,因为我们的项目名是 @liuli-util/yarn-plugin-changed。还是我遗漏了什么配置?

复现步骤

现有的包是怎么做的?

答案

是 bundler 刻意的行为,无法直接改变。。。

获取系统信息

npx envinfo --preset jest
1

使用 yarn patch 制作本地补丁包

官方文档 yarn patchopen in new window

动机

主要是处理一些 npm 模块可能存在小问题但又来不及提 PR 的情况下,在本地修改并生成 git patch 文件,在每次 yarn install 时合并这些 patch。

使用步骤

  1. 使用 yarn patch winbox 生成临时目录
  2. 修改目录中的文件
  3. 提交修改并生成 patch yarn patch-commit "C:\Users\rxliuli\AppData\Local\Temp\xfs-f35b52d4\user" -s,保存位置在 .yarn/patches/
  4. 修改 package.json 使用 patch 协议 { "winbox": "patch:[email protected]#../../../.yarn/patches/winbox-npm-0.2.0-8ddb0784dd" }
  5. 重新安装依赖 yarn

patch-package 目前无法在 yarn2 workspaces 下正常使用,参考:https://github.com/ds300/patch-package/issues/132open in new window

yarn pack 会忽略 .gitignore

官方将之写死到代码里了,参考: https://github.com/yarnpkg/berry/blob/7ae458b8165ad53a8ef9db0060cbb6de73305768/packages/plugin-pack/sources/packUtils.ts#L26-L39open in new window

是的,yarn pack 永远会忽略它,如果需要,可能需要将之打包到 js 代码里,然后在 postinstall 时写入文件(虽然会炸掉 yarn2 pnp 模式就是了)

运行初始化命令但卡死了

可能会报下面这个错

The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph
1

检查运行的命令中是否包含递归调用,即在 setup 命令中调用了 setup 命令

需要开发类似 yarn.build 的 cli,可以更好的看到到底是哪个模块运行命令卡住了,可以统计一共用了多长时间。

也有可能是其中部分模块运行命令失败,但并未返回 code 1。

如何更新所有依赖的版本为最新

众所周知,yarn1 提供了 yarn ugprade --latest 来更新所有版本至最新,但 yarn2 不存在这个命令,唯一的官方插件 upgrade-interactiveopen in new window 是需要在交互式 cli 中选择,而且还在使用代理时存在 bug。

yarn dlx npm-check-updates -u && yarn
1

yarn 插件没有版本的概念

例如现在当我的 yarn 版本是 2,引入 workspaces 插件就会报错。。。它总会下载最新版本的插件

Usage Error: This plugin cannot access the package referenced via typanion which is neither a builtin, nor an exposed entry (when initializing @yarnpkg/plugin-workspace-tools, defined in /C:/Users/rxliuli/Code/Web/demo/yarn2-error/.yarnrc.yml)
1

yarn workspaces 的 --topological-dev 参数未生效

示例 github 仓库open in new window

当我使用 --topological-dev 参数时,它并不会正常按照依赖顺序运行

$ yarn workspaces foreach -v -p --topological-dev run setup
➤ YN0000: [a]: Process started
➤ YN0000: [b]: Process started
➤ YN0000: [a]: a
➤ YN0000: [a]: Process exited (exit code 0), completed in 0s 88ms
➤ YN0000: [b]: b
➤ YN0000: [b]: Process exited (exit code 0), completed in 0s 82ms
➤ YN0000: Done in 0s 101ms
1
2
3
4
5
6
7
8

这似乎是一个回归错误,只有 yarn3 才会出现,目前无法使用 yarn2 安装 workspaces 插件,这是另一个错误。

复现步骤

  1. git clone https://github.com/rxliuli/yarn2-workspaces-error.git
  2. cd yarn2-workspaces-error
  3. yarn
  4. yarn workspaces foreach -v -p --topological-dev run setup

github issueopen in new window

在没有任何变化的情况使用 yarn 重新安装依赖仍然要很久,我不确定发生了什么,我们有 30 个模块,还包含嵌套的 workspace。

下面是输出

➤ YN0000: │ Some peer dependencies are incorrectly met; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code
➤ YN0000: └ Completed in 0s 480ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 2s 765ms
➤ YN0000: ┌ Link step
➤ YN0062: │ [email protected]:[email protected]%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=1cc4b2 The platform win32 is incompatible with this module, link skipped.
➤ YN0062: │ [email protected]:[email protected]%3A1.2.13#~builtin<compat/fsevents>::version=1.2.13&hash=1cc4b2 The platform win32 is incompatible with this module, link skipped.
➤ YN0000: └ Completed in 38s 348ms
➤ YN0000: Done with warnings in 41s 807ms
1
2
3
4
5
6
7
8
9

github issueopen in new window

看起来像是因为嵌套 workspace 引起的,有一个 PR 正在尝试修复这个问题。

如何在 monorepo 中执行指定脚本

有时候想在所有模块运行脚本做一些批量迁移的工作

npm i -g esno
yarn workspaces foreach -pvA exec "esno <脚本绝对路径>"
1
2

yarn 如何全局安装 monorepo 项目中包含私有模块依赖的 cli 模块

  1. 将所有的私有模块移至 devDependencies
  2. 使用 esbuild 等工具将所有依赖打包到 bundle
  3. npm i -g . 安装依赖即可
  4. 全局使用命令

这里的核心问题是全局安装依赖时无法找到 monorepo 中未发布的模块,但私有模块在全局找不到,也无法作为依赖安装,所以只能使用这种奇怪的方法了。另一种解决方案是递归将所依赖的私有模块全部全局安装(没有尝试过)。

yarn 无法显式修改指定版本

这是一个已知错误,目前尚未发布: https://github.com/yarnpkg/berry/issues/3322open in new window

下面是控制台日志,这种行为很明显是有问题的,因为吾辈只希望能在项目中测试另一个项目的某个模块。

➤ YN0071: │ Cannot link @liuli-util/cli into [email protected]:libs/joplin-api dependency [email protected]:10.0.0 conflicts with parent dependency [email protected]:9.1.0
➤ YN0071: │ Cannot link @liuli-util/cli into [email protected]:apps/joplin-blog dependency [email protected]:8.2.0 conflicts with parent dependency [email protected]:7.2.0
➤ YN0071: │ Cannot link @liuli-util/cli into [email protected]:apps/joplin-blog dependency [email protected]:10.0.0 conflicts with parent dependency [email protected]:9.1.0
➤ YN0071: │ Cannot link @liuli-util/cli into joplin-blog$wsroot[email protected]workspace:apps/joplin-blog dependency [email protected]:8.2.0 conflicts with parent dependency [email protected]:7.2.0
➤ YN0071: │ Cannot link @liuli-util/cli into joplin-blog$wsroot[email protected]workspace:apps/joplin-blog dependency [email protected]:10.0.0 conflicts with parent dependency [email protected]:9.1.0
➤ YN0071: │ Cannot link @liuli-util/cli into joplin-api$wsroot[email protected]workspace:libs/joplin-api dependency [email protected]:10.0.0 conflicts with parent dependency [email protected]:9.1.0
➤ YN0000: └ Completed in 0s 761ms
➤ YN0000: Failed with errors in 4s 996ms
1
2
3
4
5
6
7
8

目前似乎只能依赖于手动复制粘贴解决。。。