随着项目的不断增大,子项目拆分的越来越多,开发过程可能会遇到如下的两个问题:
- 多个项目具有相同的外部依赖,导致重复安装,占用磁盘空间
- 多个项目之间相互依赖,依赖之间的更新流程繁琐(需要发布到npm,或者使用npm link)
开源社区中有lerna这样的优秀的monorepo管理工具来解决这两个问题。但其实,这应该属于npm包管理工具自身的问题范围。所以作为后起之秀的yarn,就希望能够通过其原生的workspace去解决这些问题:
下面是一个基于yarn workspace测试项目的目录结构,可以看出,虽然有foo
,bar
两个子项目,但二者的依赖共同维护于父项目之中。进而解决了多个项目重复外部依赖的问题。
test-yarn-workspace
├─ .gitignore
├─ node_modules // 该目录下包含了foo、bar两个子项目的所有依赖。
├─ package.json
├─ packages
│ ├─ bar
│ │ ├─ index.html
│ │ ├─ main.js
│ │ ├─ package.json
│ │ └─ webpack.config.js
│ └─ foo
│ ├─ App.vue
│ ├─ index.html
│ ├─ main.js
│ ├─ package.json
│ └─ webpack.config.js
└─ yarn.lock
值得注意的,针对于上述的项目,yarn仅仅是维护了目录结构,实际上,javascript的模块查找过程中,若当前node_modules中找不到对应的依赖,则会去父级目录查找。即实现公共依赖这一功能的核心其实是node_modules的查找机制。
比如某个文件所在路径是: ‘/home/ry/projects/foo.js’ ,当按照如下方式引用模块 require(‘bar.js’),那么会依次查找以下路径
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
针对于第二个问题,即解决多个项目之间的依赖,yarn同样采用了软连接的方式。即将/node_modules/foo作为/packages/foo的软连接,进而实现packages内的相互依赖。
以上,在父项目的node_modules目录中创建packages/foo的软连接。即让其他子项目可以直接依赖于foo。
比如bar中可以这样直接把foo作为其依赖
{
"name": "bar",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"foo": "1.0.0"
},
"devDependencies": {
"webpack": "^5.23.0",
"webpack-cli": "^4.5.0"
}
}
值得注意的,根据以上的分析,packages中子项目的目录结构应该不会再有node_modules,因为他们的依赖都在父项目中。我们看到的目录结构也确实如此。但当子项目中依赖的模块有命令行工具时候,比如webpack-cli,依旧会生成这个目录。
test-yarn-workspace
├─ .gitignore
├─ package.json
├─ packages
│ ├─ bar
│ │ ├─ index.html
│ │ ├─ main.js
│ │ ├─ node_modules
│ │ │ └─ .bin
│ │ │ ├─ webpack
│ │ │ ├─ webpack-cli
│ │ │ ├─ webpack-cli.cmd
│ │ │ └─ webpack.cmd
│ │ ├─ package.json
│ │ └─ webpack.config.js
│ └─ foo
│ ├─ App.vue
│ ├─ dist
│ │ ├─ index.html
│ │ ├─ main.js
│ │ └─ main.js.LICENSE.txt
│ ├─ index.html
│ ├─ main.js
│ ├─ node_modules
│ │ └─ .bin
│ │ ├─ webpack
│ │ ├─ webpack-cli
│ │ ├─ webpack-cli.cmd
│ │ └─ webpack.cmd
│ ├─ package.json
│ └─ webpack.config.js
├─ README.md
└─ yarn.lock
以上的目录结构中子项目出现了node_modules是因为.bin文件夹中的cli命令不会向node_modules一样去自动查找父目录中的文件。