前言
npm 自从v7开始,引入了一个非常弱小的性能,那就是workspaces
。另外,yarn和pnpm也领有workspaces
的能力。不过,从用法上来说,简直是截然不同的。所以,学会了npm workspaces的话,自然而然也就学会了yarn和pnpm的了。
概览
本文会分四个局部进行介绍:
- 什么是workspaces;
- 多包治理;
- 多项目管理;
- 避坑;
- 总结;
什么是workspaces?
顾名思义,workspaces就是多空间的概念,在npm中能够了解为多包。它的初衷是为了用来进行多包治理的,它能够让多个npm包在同一个我的项目中进行开发和治理变得十分不便:
- 它会将子包中所有的依赖包都晋升到根目录中进行装置,晋升包装置的速度;
- 它初始化后会主动将子包之间的依赖进行关联(软链接);
- 因为同一个我的项目的关系,从而能够让各个子包共享一些流程,比方:eslint、stylelint、git hooks、publish flow等;
这个设计模式最后来自于Lerna,但Lerna对于多包治理,有着更强的能力,而且最新版的Lerna能够齐全兼容npm或yarn的workspaces模式。不过因为本文讲的是workspaces,所以,对于Lerna有趣味的同学,能够自行去Lerna官网学习。
多包治理
多包治理下面曾经说过它绝对单包独自治理的益处。所以,咱们通过实例的例子来让同学们感受一下workspaces为什么被我吹的这么牛批。
例子演示
我的项目地址我挂在github上了,有趣味的同学能够自行查看源码。
1. 降级npm到7或最新版
npm i -g npm@latest
2. 创立我的项目
mkdir demo-workspaces-multi-packages
3. 初始化我的项目
npm init -y
.
└── package.json
4. 申明本我的项目是workspaces模式
package.json
新增配置:
"private":"true",
"workspaces": [
"packages/*"
],
这里的packages/*
示意咱们的子包都在packages
文件夹下。(对于workspaces的细节和更多用法本文不会一一介绍,文档十分分明,本文考究实战)
5. 初始化子包m1
创立子包m1
:
npm init -w packages/m1 -y
.
├── package.json
└── packages
└── m1
└── package.json
创立m1
的主文件index.js
:
echo "exports.name = 'kitty'" >> packages/m1/index.js
.
├── package.json
└── packages
└── m1
├── index.js
└── package.json
6. 初始化子包m2
同样的形式,创立子包m2
:
npm init -w packages/m2 -y
.
├── package.json
└── packages
├── m1
│ ├── index.js
│ └── package.json
└── m2
└── package.json
创立m2
的主文件index.js
:
echo "const { name } = require('m1')\nexports.name = name" >> packages/m2/index.js
.
├── package.json
└── packages
├── m1
│ ├── index.js
│ └── package.json
└── m2
├── index.js
└── package.json
因为这里require('m1')
,所以须要增加m1
依赖到m2
的package.json
中:
npm i -S m1 --workspace=m2
7. 初始化子包demo
为了不便咱们看到成果,再创立一个demo
文件夹(多包治理举荐搞个demo子包进行整体成果测试):
npm init -w packages/demo -y
echo "const { name } = require('m2')\nconsole.log(name)" >> packages/demo/index.js
.
├── package.json
└── packages
├── demo
│ ├── index.js
│ └── package.json
├── m1
│ ├── index.js
│ └── package.json
└── m2
├── index.js
└── package.json
额定的,这个demo包,咱们并不像他进行公布,为了避免不小心公布,咱们在demo
的package.json
中新增:
"private":"true",
因为这里require('m2')
,所以须要增加m2
依赖到demo
的package.json
中:
npm i -S m2 --workspace=demo
咱们看看这时候我的项目根目录的node_modules
吧:
<img width=”300px” src=”https://user-images.githubusercontent.com/17001245/148692468-e94beeca-3205-40f9-9e2b-b4c145a2f4a4.png”>
是不是很有意思?全是软链接,链接的指向就是packages
文件夹下的各子包。
OK,搞了半天,咱们运行demo
看下成果吧:
node packages/demo/index.js
# 输入:
kitty
通过下面的例子,咱们能够看出,workspaces
对于本地子包之间的依赖解决的十分奇妙,也让开发者更加不便,尤其是多人开发的时候。另一个人在拉取完我的项目当前,只须要运行npm install
,即可进行开发,软链接会主动建设好。
接下来,咱们看workspaces
我的项目中如果装置三方包的状况。
8. 装置两个不同版本的包
npm i -S vue@2 --workspace=m1
npm i -S vue@3 --workspace=m2
例子中,咱们想看看,因为咱们的包都会被晋升到根目录进行装置,那么不同版本的vue
它会怎么解决呢?难道只会装置vue3
的包吗?
后果:
<img width=”300px” src=”https://user-images.githubusercontent.com/17001245/148693126-3426d7b8-a011-4634-87e4-e1e52e5c798b.png”>
这样,咱们就无需放心版本抵触的问题了,workspaces
显然曾经很好地解决了。
重点参数--workspace
在workspaces
我的项目中,一个很外围的参数就是--workspace
,因为从前文的安装包到子包的命令能够发现,和传统的安装包一样,都是应用npm i -S 包名
或者npm i -D 包名
,不同的仅仅是开端加了--workspace
。
那是不是对于其它的命令,比方run
、version
、publish
等也是样的应用形式呢?答案是:Yes!
另外,如果咱们子包的package.json
中scprits
全都有一个叫test
的命令,咱们想一次性运行所有子包的这个命令,能够应用npm run test --workspaces
即可。
这样的话,对于咱们的Lint校验或是单测都是十分不便的。
到此,workspaces在多包治理中启到的作用就根本介绍完了。值得一提的是,多包治理,理论我的项目中还是举荐应用Lerna
,它对于版本依赖主动降级、发包提醒、主动生成Log(Change Log / Release Note)、CI等都具备一套非常成熟的流程机制了。
多项目管理
目前的npmworkspaces
,集体认为是非常适合用来做多我的项目的整合(Monorepo)治理的 。
例子演示
我的项目地址我挂在github上了,有趣味的同学能够自行查看源码。
1. 创立我的项目
mkdir demo-workspaces-multi-project
2. 初始化我的项目
npm init -y
.
└── package.json
3. 申明本我的项目是workspaces模式
package.json
新增配置:
"private":"true",
"workspaces": [
"projects/*"
],
4. 初始化子项目zoo
创立子项目zoo
:
npm init -w projects/zoo -y
.
├── package.json
└── packages
└── zoo
└── package.json
创立模板文件index.html
,主内容为:
<!-- projects/zoo/index.html -->
<body>
<h1>Welcome to Zoo!</h1>
<div id="app"></div>
</body>
创立我的项目入口js文件index.js
,内容为:
console.log('Zoo')
装置我的项目构建依赖包:
npm i -S webpack webpack-cli webpack-dev-server html-webpack-plugin webpack-merge --workspace=zoo
# projects/zoo/package.json
"private":"true",
"dependencies": {
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2"
}
创立webpack配置:
// projects/zoo/webpack/base.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
function resolve(dir) {
return path.join(__dirname, '../' + dir)
}
exports.config = {
entry: resolve('src/index.js'),
plugins: [
new HtmlWebpackPlugin({
title: 'Zoo',
filename: 'index.html',
template: resolve('src/index.html')
})
],
}
exports.resolve = resolve
// projects/zoo/webpack/dev.config.js
const { config, resolve } = require('./base.config')
const { merge } = require('webpack-merge')
exports.default = merge(config, {
mode: 'development',
output: {
filename: 'bundle.js',
},
})
// projects/zoo/webpack/prod.config.js
const { config, resolve } = require('./base.config')
const { merge } = require('webpack-merge')
exports.default = merge(config, {
mode: 'production',
output: {
filename: 'bundle.js',
},
})
zoo下的package.json
新增命令:
"scripts": {
"dev": "webpack-dev-server --config webpack/dev.config.js --open",
"prod": "webpack --config webpack/prod.config.js"
},
接下来就能够运行了,只须要在我的项目根目录应用:
npm run dev --workspace=zoo
即可进行本地开发。
成果:
<img width=”200px” src=”https://user-images.githubusercontent.com/17001245/148756703-5d1a71e7-ecf3-4d56-947d-2a8ad2f2c4d1.png”>
运行prod同理。
5. 初始化子项目shop
创立子项目shop
:
npm init -w projects/shop -y
其余步骤同初始化子项目zoo
简直截然不同,所以不再赘述。
最初的目录构造:
<img width=”150px” src=”https://user-images.githubusercontent.com/17001245/148758564-84a086c0-caf1-4042-b6e7-d0e232787490.png”>
共享
对于Monorepo,共享是最重要的一个劣势。所以,咱们来做一些共享的事件。
1. 在根目录创立share
文件夹,作为共享资源目录,并创立共享文件Fish.js
:
mkdir share
mkdir share/js
touch share/js/Fish.js
// share/js/Fish.js
class Fish {
constructor(name, age) {
this.name = name
this.age = age
}
swim() {
console.log('swim~')
}
print() {
return '🐟 '
}
}
module.exports = Fish
2. 子项目中的webpack
配置新增alias
子项目zoo
和shop
都加上雷同的alias
即可:
resolve: {
extensions: ['.js'],
alias: {
'$share': resolve('../../share'),
},
},
子项目zoo
的入口文件改为:
// projects/zoo/src/index.js
const Fish = require('$share/js/Fish')
const fish = new Fish()
document.getElementById('app').textContent = fish.print()
运行zoo
的dev
看成果:
<img width=”150px” src=”https://user-images.githubusercontent.com/17001245/148770309-3fe66464-6b1e-4780-948b-6873ee4cab5e.png”>
批改子项目shop
的入口文件后,会呈现同样的成果。
也就是说,share文件夹下的货色,zoo
和shop
能够专用了,须要做的仅仅是新增一个webpack的alias
而已!🎉
🤔思考 —— 咱们为什么应用workspaces
做汇合我的项目,用传统形式不行吗?
传统形式:
- 各个子项目都汇合到一个我的项目中来。和上文不同的是,
package.json
只有一份,在根目录,所有我的项目中的npm包都装置到根目录,在根目录的package.json
中定义开发和部署子项目的命令; - 各个子项目都汇合到一个我的项目中来。和上文不同的是,尽管根目录和各个子包都各自有一份
package.json
,但根底的构建工具在根目录进行装置,比方下面提到的webpack
、webpack-cli
、webpack-dev-server
、html-webpack-plugin
、webpack-merge
,全都在根目录进行装置,和业务相干的npm包都装置到各自子项目中; - 各个子项目都汇合到一个我的项目中来。和上文不同的是,各个子包都各自有一份
package.json
,根目录无package.json
;
形式1 —— 毛病:
- 命令凌乱;
- 无奈应答子项目之间存在npm包抵触的问题;(比方,A我的项目想用webpack4,B我的项目想用webpack5;或者A我的项目想用Vue2,而B我的项目想用Vue3)
形式2 —— 毛病:
- 如果子项目有雷同的包,不得不在各个子项目中反复装置;
- 同样无奈应答子项目之间存在npm包抵触的问题;(比方,A我的项目想用webpack4,B我的项目想用webpack5)
- 如果某天想把B我的项目移除,老本很高;
形式3 —— 毛病:
- 如果子项目有雷同的包,不得不在各个子项目中反复装置;
那应用workspaces
就很好的解决了下面的所有问题!
另外,对于曾经存在的我的项目而言,比方我往年所接手的我的项目,一个是Web的,一个是Wap的,而后发现,因为他们属于同一个业务,所以有大量的代码能够复用,又因为只波及这两个我的项目而已,把公共代码做成npm包又有点太杀鸡用牛刀,所以,过来始终采纳的是复制、粘贴的模式。这显然是十分低效的。另外就是,mock服务也是个字我的项目独自一套,然而大多数接口的数据都是能够专用的,只是url前缀不同。最离谱的就是几百个银行图标都截然不同。所以,我打算将它俩合并成一个我的项目。而workspaces
对于我来说,是一个对原我的项目改变量最小的计划。
怎么独自部署?
咱们想要在构建机上只部署我的项目zoo
,应该怎么做?
1. 装置依赖包
npm install --production --workspace=zoo
这样的话,构建机上就只会装置zoo
我的项目下的依赖包了。
2. 构建
npm run prod --workspace=zoo
这样的话,就构建胜利了!
避坑
npm的workspaces其实有暗藏的坑,所以我也列举下。
坑一:npm install 默认模式的坑
npm v7开始,install会默认装置依赖包中的peerDependencies
申明的包。新我的项目可能影响不大,然而,如果你是革新现有的我的项目。因为用了对立治理的形式,所以个别都会把子我的项目中的lock文件删掉,在根目录用对立的lock治理。而后,当你这么做了当前,可能坑爹的事件就呈现了。
场景:我的子项目中用的是webpack4
,而后,咱们的构建相干的工具(webpack、babel、postcss、eslint、stylint等)都会封装到根底包中。这些包的依赖包中有一个包,在package.json
申明中应用这样写:
"peerDependencies": {
"webpack": "^5.1.0"
},
而后,在根目录中npm install
,而后再跑子项目发现我的项目跑不起来了。起因就是,我的项目竟然装置的是webpack5
的版本!
解决方案
- 计划1:在子项目的
package.json
中显示申明用的webpack
版本; - 计划2:去github和作者磋商修复依赖包,如果他的包即兼容
webpack4
也兼容webpack5
,应该写成,把申明改为:"webpack": "^4.0.0 || ^5.0.0"
- 计划3:
npm install --legacy-peer-deps
集体真的感觉这是npm作者脑袋被驴踢了。对于yarn或者pnpm,他们的workspaces
都不会用这种默认装置peerDependencies
的模式。
作者本来是想,因为如果npm包的开发者申明了peerDependencies
,如果咱们应用过程中没有装置匹配的版本的包就可能导致我的项目跑不了,为了方便使用,他就采纳了默认装置的模式。
然而,这种做法会导致那些peerDependencies
不合乎书写标准的包,在我的项目中配合应用呈现问题。而且,即便新的包中包作者们开始留神书写标准,然而无奈解决那些曾经公布进来的老包,总不可能全都回收,而后一个个版本从新再公布一遍吧!
坑二:小版本包抵触
这其实是集体大意导致的。
举个例子:zoo
应用命令npm i -S @vue2.2.1
引入vue,shop
应用命令npm i -S @vue2.2.2
引入vue。那么,我的项目会有两个版本的vue
吗?不会。
起因咱们能够看zoo
我的项目下的package.json
:
"dependencies": {
"html-webpack-plugin": "^5.5.0",
"vue": "^2.2.1",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2",
"webpack-merge": "^5.8.0"
}
豁然开朗。
解决方案
- 计划1:其实去掉
^
即可; - 计划2:咱们装置的时候能够应用
npm i --save-exact vue@2.2.1 --workspace=zoo
总结
本文,利用了workspaces
来做多包治理,以及多项目管理,体现出了workspaces
的弱小。因为我集体负责的我的项目始终以来都是应用npm来治理的,所以想要迁徙到yarn或者pnpm存在未知的危险,而且,也尝试过,因为一些老包yarn2和pnpm都跑不起来。对于新的我的项目,集体也更举荐yarn2或者pnpm进行治理,它们比npm更加弱小。
本原文来自于集体github博客,感觉好的小伙伴能够点个赞哈~
<( ̄▽ ̄)/
文中多包治理和多项目管理的源码别离在:
- 多包治理
- 多项目管理
有趣味的同学能够自行下载学习。
发表回复