动机
- 不再同时使用两个 monorepo 管理工具,或者说将 monorepo 功能整合到 yarn 里(新的依赖管理工具 npm7/pnpm/rush 都是这样干的),专注于增强 yarn(yarn.build 是一个不错的例子)
- 使用 yarn2 逐渐发展的生态(yarn1 基本没什么更新了)
- lerna 的高级功能不太好用,主要是
lerna run
不支持缓存导致每次重新构建会很烦 -- 考虑使用 ultra 支持,但 yarn2 似乎有插件可以支持,参考: https://github.com/yarnpkg/berry/issues/2374 - 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_modulesbin- 该优化大约能将每个 cli 的构建时间从 40s 降低至 10s 内,之前大部分时间都是写入
node_modules/.bin
- 该优化大约能将每个 cli 的构建时间从 40s 降低至 10s 内,之前大部分时间都是写入
如何使用类似于 lerna run/exec 之类的命令
- 使用 workspaces 插件
yarn plugin import @yarnpkg/plugin-workspace-tools
- 使用
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
如何像 lerna publish 更新所有模块的版本
升级全部模块目前可以使用 yarn workspaces foreach
模拟 lerna publish
,但对于独立模式则是另外一套完全不同的模式了。
yarn workspaces foreach exec yarn version patch
缺点是无法按照是否修改决定是否发布新版本。。。
发布所有包
yarn npm publish
仍有 bug,所以只能直接npm publish
了
yarn workspaces foreach -A --no-private exec npm publish
感觉还是需要实现缓存的功能,只是将缓存文件加到 git 而已。考虑到不是所有命令都需要,也许可以使用缓存目录。
如果包含 cli 子模块怎么办?
打包完成之后 yarn
安装即可,yarn 会负责将命令写入到依赖的其他子模块的 node_modules/.bin/ 中。
编写插件
问题
- 不能使用 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-35034
- yarn 2 的社区接受度似乎极低,github 上依赖它的库不超过 100 个,参考:https://github.com/yarnpkg/berry/network/dependents?package_id=UGFja2FnZS03MDE5NDg3MjU%3D -- 实际上这是错的,由于 yarn2 不在 package.json 中声明而已,目前 storybook 等流行库也升级了 yarn2,但它们也混用 lerna 和 nx。。。
- 无法使用
patch-package
,但可以使用yarn patch
命令。。。
其他优点
- 原生支持 workspaces
- 有插件 api,可以为自定义需求编写插件
- cli 命令执行不再强绑定到 node_modules/.bin
- 重新安装依赖时时不需要停止
vite dev
(目前无法安装成功)
编写插件之后应该怎么在本地测试?
单元测试
const dir = npath.toPortablePath(path.resolve('')) const configuration = await Configuration.create(dir, dir) const project = (await Project.find(configuration, dir)).project
yarn plugin import <>
yarn workspace 有办法包含一个不在 workspace 管理的子目录么?
例如项目可能有一个 website 目录用以存放网站的文档,而又不希望将之放到 yarn workspace 管理(有可能是不同的技术栈,例如 vuepress)。
在 workspace
字段中排除,然后在目录中添加空的 yarn.lock
文件
npmrc => yarnrc.yml
yarn2 在 monorepo 中根模块无法直接 import 子模块的 yarn 插件
复现步骤
yarn && yarn setup
安装依赖及初始化cd libs/yarn-plugin-changed && yarn build
打包插件cd ../.. && yarn plugin import libs/yarn-plugin-changed/bundles/\@yarnpkg/plugin-changed.js
回到根目录安装插件- 得到错误
$ 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
答案
必须使用 . 开头的相对路径
yarn 插件打包后在 bundles/@yarnpkg 下而非我们的组织名下面
同样是在编写 yarn 插件时遇到的错误,在我们使用 yarn plugin import 安装插件之后,才发现安装的目录是 .yarn/plugins/@yarnpkg/plugin-changed.cjs,这似乎有些不对,因为我们的项目名是 @liuli-util/yarn-plugin-changed
。还是我遗漏了什么配置?
复现步骤
现有的包是怎么做的?
- yarn.build: 使用系统命令手动移动文件,同时替换打包之后的 name 字段
答案
是 bundler 刻意的行为,无法直接改变。。。
获取系统信息
npx envinfo --preset jest
使用 yarn patch 制作本地补丁包
动机
主要是处理一些 npm 模块可能存在小问题但又来不及提 PR 的情况下,在本地修改并生成 git patch 文件,在每次 yarn install
时合并这些 patch。
使用步骤
- 使用
yarn patch winbox
生成临时目录 - 修改目录中的文件
- 提交修改并生成 patch
yarn patch-commit "C:\Users\rxliuli\AppData\Local\Temp\xfs-f35b52d4\user" -s
,保存位置在 .yarn/patches/ - 修改 package.json 使用 patch 协议
{ "winbox": "patch:[email protected]#../../../.yarn/patches/winbox-npm-0.2.0-8ddb0784dd" }
- 重新安装依赖
yarn
patch-package 目前无法在 yarn2 workspaces 下正常使用,参考:https://github.com/ds300/patch-package/issues/132
yarn pack 会忽略 .gitignore
官方将之写死到代码里了,参考: https://github.com/yarnpkg/berry/blob/7ae458b8165ad53a8ef9db0060cbb6de73305768/packages/plugin-pack/sources/packUtils.ts#L26-L39
是的,yarn pack 永远会忽略它,如果需要,可能需要将之打包到 js 代码里,然后在 postinstall 时写入文件(虽然会炸掉 yarn2 pnp 模式就是了)
运行初始化命令但卡死了
可能会报下面这个错
The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph
检查运行的命令中是否包含递归调用,即在 setup
命令中调用了 setup
命令
需要开发类似 yarn.build 的 cli,可以更好的看到到底是哪个模块运行命令卡住了,可以统计一共用了多长时间。
也有可能是其中部分模块运行命令失败,但并未返回 code 1。
如何更新所有依赖的版本为最新
众所周知,yarn1 提供了 yarn ugprade --latest
来更新所有版本至最新,但 yarn2 不存在这个命令,唯一的官方插件 upgrade-interactive 是需要在交互式 cli 中选择,而且还在使用代理时存在 bug。
yarn dlx npm-check-updates -u && yarn
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)
--topological-dev
参数未生效
yarn workspaces 的 当我使用 --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
这似乎是一个回归错误,只有 yarn3 才会出现,目前无法使用 yarn2 安装 workspaces 插件,这是另一个错误。
复现步骤
git clone https://github.com/rxliuli/yarn2-workspaces-error.git
cd yarn2-workspaces-error
yarn
yarn workspaces foreach -v -p --topological-dev run setup
yarn install link step 很慢
在没有任何变化的情况使用 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
看起来像是因为嵌套 workspace 引起的,有一个 PR 正在尝试修复这个问题。
如何在 monorepo 中执行指定脚本
有时候想在所有模块运行脚本做一些批量迁移的工作
npm i -g esno
yarn workspaces foreach -pvA exec "esno <脚本绝对路径>"
yarn 如何全局安装 monorepo 项目中包含私有模块依赖的 cli 模块
- 将所有的私有模块移至
devDependencies
- 使用 esbuild 等工具将所有依赖打包到 bundle
npm i -g .
安装依赖即可- 全局使用命令
这里的核心问题是全局安装依赖时无法找到 monorepo 中未发布的模块,但私有模块在全局找不到,也无法作为依赖安装,所以只能使用这种奇怪的方法了。另一种解决方案是递归将所依赖的私有模块全部全局安装(没有尝试过)。
yarn 无法显式修改指定版本
这是一个已知错误,目前尚未发布: https://github.com/yarnpkg/berry/issues/3322
yarn link 会强制检查依赖是否冲突
下面是控制台日志,这种行为很明显是有问题的,因为吾辈只希望能在项目中测试另一个项目的某个模块。
➤ 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
目前似乎只能依赖于手动复制粘贴解决。。。