Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入理解 yarn 中的 nohoist 机制 #72

Open
chenxiaochun opened this issue Jun 11, 2020 · 9 comments
Open

深入理解 yarn 中的 nohoist 机制 #72

chenxiaochun opened this issue Jun 11, 2020 · 9 comments

Comments

@chenxiaochun
Copy link
Owner

当前的问题是什么?

首先,让我们先快速回顾一下在一个独立项目中的hoist工作机制:

为了减少项目中的依赖包冗余,很多项目开发者会使用某种hoist机制将公共的依赖包提取出来,并将其展开集中到同一个目录下。

下图中左边的示例是一个常见的普通项目依赖树结构:

通过使用hoist机制,我们可以尽可能消除对[email protected][email protected]两个包的重复依赖安装,同时还不会改变对[email protected]的依赖维护关系。而我们知道,大多数模块爬虫、loaders和打包器都是通过遍历项目中的node_modules来定位依赖包的。

那么在monorepo类型的项目中,引入了一种新的层级结构,它不再需要必须使用node_modules来建立模块间的依赖关系。在这种项目中,模块可以分散在项目中的多个位置:

yarn workspaces可以通过将公共依赖包提升到所有子项目的父目录的node_modules中,以实现共享这些依赖包的机制。而且如果这些依赖包彼此之间也有依赖关系时,使用这种优化方式的好处会更优。

无法找到模块!

并不是所有的模块爬虫都会遍历模块的软链接。因此,有可能我们在每个子项目中进行编译打包时,会出现模块无法找到的情况。

看上图右侧的项目结构,我们在实际打包时,可能会出现以下情况:

在这个monorepo项目中,依赖包可能存在于任意位置。它就需要模块爬虫能够遍历每一个node_modules目录才可能找到对应的模块。

为什么这些模块的位置不能固定下来呢?

事实上有很多开发都提出过此类问题的解决办法,例如:可以建立多个根目录、可以自定义模块映射、更智能的模块遍历模式等。但不管怎样,还是有一些原因使得问题难以解决:

  1. 并不是所有的第三方库都会适配 monorepo 环境。
  2. js 有大量的第三方库。而这些大量的第三方组成的工具链也就成了最”薄弱“的一环。因为只要有一个模块没有进行适配,就可能导致整个工具链都没法使用了

什么是nohoist

那有没有一种简单的机制能够使这些不兼容的库可以正常在 monorepo 中工作呢?

那就是yarn提供的nohoist机制,这种机制在lerna中也有相关演示。

nohoist机制可以使workspace去自定义处理那些不兼容hoist模式的第三方库。只要你进行了配置,它就不会把这些再模块提升到根目录。它们还是被放在原来的子项目中,就像运行在一个标准的、没有workspace的工程一样。

一些注意事项

nohoist虽然很有用,但是它还是有一些缺点。最明显的就是指定的nohoist模块依然会被重复安装在多个目录中,这也就完全违背了上面我们提到的多种好处。

建议在指定nohoist的范围时,尽量越小、越精确越好。

如何使用它?

nohoist的使用方法非常简单。在package.json中进行定义相应的规则即可。yarn是从1.4.2版本开始提供此功能的。

下面是它的类型定义:

export type WorkspacesConfig = {
  packages?: Array<string>,
  nohoist?: Array<string>,
};

使用示例:

"workspaces": {
  "packages": ["packages/*"],
  "nohoist": ["**/react-native", "**/react-native/**"]
}

nohoist支持使用 glob 语法匹配模块路径依赖目录。模块目录是一个虚拟目录,不用带上node_modulespackages等路径。

进一步说明

让我们通过下面一个伪项目结构,来说明nohoist机制是如何阻止react-native模块被提升的。在下面的monorepo项目中主要有 A、B、C 三个子项目:

在执行yarn install命令以前,它们的文件目录结构如下:

package.json文件们于monorepo的根目录下

...
"name": "monorepo",
"private": true,
"workspaces": {
  "packages": ["packages/*"],
  "nohoist": ["**/react-native", "**/react-native/**"]
}
...
  • 正确设置private属性

nohoistworkspaces只能在private: true的项目中工作。

  • glob 模式匹配

yarn内部,它会基于每个模块的原始依赖包(在被提升之前的结构)构建一个虚拟的模块路径。如果nohoist匹配到了其中的路径,它就会被离其最近的子项目所代替。

  • 模块路径

在 A 项目中:

monorepo/A
monorepo/A/react-native
monorepo/A/react-native/metro
monorepo/A/Y

在 B 项目中:

monorepo/B
monorepo/B/X
monorepo/B/X/react-native
monorepo/B/X/react-native/metro

在 C 项目中:

monorepo/C
monorepo/C/Y
  • nohoist模式

**/react-native,这会告诉yarn不要去提升react-native模块,不管它的位置在哪里。

**/react-native/**,这会告诉yarn不要去提升react-native的任何依赖包。

这两种模式结合起来就是告诉yarn不要去提升react-native以及它的所有依赖包。

如何关闭nohoist

只要在一个私有的package.json中添加了nohoist配置,yarn默认就会使用它。

如果想关闭nohoist,一般有三种方式:

  1. 可以在package.json中移除nohoist的相关配置。
  2. .yarnrc中添加workspaces-nohoist-experimental: false标识。
  3. 使用yarn config set workspaces-nohoist-experimental false命令来关闭它。

原文资料

@chenxiaochun chenxiaochun changed the title 深入理解 yarn workspaces 中的 nohoist 机制 深入理解 yarn 中的 nohoist 机制 Jun 12, 2020
@dbl520
Copy link

dbl520 commented Mar 26, 2021

"workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > gl-unified-permission > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > pf-user-awareness-web > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > gl-unified-permission > [email protected]" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > mq-web > [email protected]" has unmet peer dependency "webpack@^3.0.0 || ^4.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > mq-web > [email protected]" has unmet peer dependency "webpack@^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > mq-web > [email protected]" has unmet peer dependency "webpack@^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > mq-web > [email protected]" has unmet peer dependency "webpack@^4.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > pf-user-awareness-web > [email protected]" has unmet peer dependency "[email protected] || 5.x.x".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > pf-user-awareness-web > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > greeting-card-ui > @vue/eslint-config-airbnb > [email protected]" has unmet peer dependency "webpack@>=1.11.0".
warning "workspace-aggregator-1917a6ce-f14f-4b68-a050-0f6f7e8482d9 > pf-user-awareness-web > webpack-cli > @webpack-cli/[email protected]" has unmet peer dependency "[email protected] || 5.x.x".

image

安装多个项目提示的,有影响吗

@chenxiaochun
Copy link
Owner Author

看起来都是warning,应该没有影响

@TLovers
Copy link

TLovers commented Mar 20, 2022

能禁止某一个包里面的依赖吗?
好比有A 和B 两个文件夹 都依赖了vue, A依赖的是vue2 B依赖的是vue3 我只想禁止vue3的提升 而vue2正常提升 可以吗
我试了下好像并不行 不知道是不是我使用的姿势不正确

@chenxiaochun
Copy link
Owner Author

@Lovercz ,我也不太清楚。你如果有好的解决方案,也可以告诉我哈😀

@taoliujun
Copy link

A依赖B,B依赖C
当yarn add A的时候,C却未在 root node_modules下出现,是什么机制呀?

@vvizden
Copy link

vvizden commented Sep 21, 2022

同问

@radiorz
Copy link

radiorz commented Mar 15, 2023

Good job

@SoldierAb
Copy link

SoldierAb commented May 31, 2023

A依赖B,B依赖C 当yarn add A的时候,C却未在 root node_modules下出现,是什么机制呀?

确实,node_modules 依赖树是复杂、多层的。类似 vue-cli、create-react-app、react 等这类依赖较为确定的项目适合去做Monorepo。 而庞大的业务项目集中,不同项目的依赖做组成的依赖树在workpaces的组织下不见得是稳定的。

比如某个package-A, 其package.json如下:

{
  "devDependencies": {
     "vite-plugin-svg-icons": "^2.0.1",
     "vite": "^2.8.0"
  }
}

vite-plugin-svg-icons的peerDependencies 中指定 vite 依赖

{
  "peerDependencies": {
    "vite": ">=2.0.0"
  }
}

此时包含其他package-B 、 package-C, 它们有共同依赖:

{
  "devDependencies": {
     "vite": "^4.8.0"
  }
}

此时通过workspace 安装后

 - <rootDir>
 - packages
  |- package-A
  |_ node_modules
     |_ vite ^2

  |- package-B
  |_ package-C
 
 - node_modules
   |- vite ^4
   |_ vite-plugin-svg-icons ^2
     

这时导致 vite-plugin-svg-icons 引用的vite是<rootDir>/node_modules/vite^4
而不是 <rootDir>/packages/package-A/node_modules/vite

诸如此类的依赖问题可以展开。 已经在业务项目中弃用这种模式了

@NameWjp
Copy link

NameWjp commented Aug 18, 2023

感觉真正能用在生产中的两组模式:pnpm(推荐的)or lerna + yarn (nohoist 模式)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants