npm workspaces 的使用
介绍
本地化开发 npm package
不可能每次改了代码,都发布到 npm
官网,所以 npm
提供给我们 npm link
命令用来进行本地化开发。使用 npm-link
调试代码,我们要开两个编辑器,一个编辑器改开发 package
的代码,另一个编辑器用来调试。
如果我们开发组件库几十个组件,难道要开几十个窗口吗?npm
的新特性 workspaces
可以帮助我们来进行多包管理,它可以让多个 npm
包在同一个项目中进行开发和管理变得非常方便:
- 它会将子包中所有的依赖包都提升到根目录中进行安装,提升包安装的速度;
- 它初始化后会自动将子包之间的依赖进行关联(软链接);
- 因为同一个项目的关系,从而可以让各个子包共享一些流程,比如:
eslint
、stylelint
、git hooks
、publish flow
等;
workspaces
是一个用来在本地项目下面管理多个包的 npm
功能。(yarn 很早就支持了,npm
在 7.x
中开始支持,也就是 Node@15.0.0 新增的功能)。
这个功能让我们在本地开发包,尤其是多个互相依赖的包时,避免手动的去执行 npm link
命令,而是在 npm install
的时候,会自动把 workspaces
下面的合法包,自动创建符号链接到当前的 node_modules
文件夹里。
使用
配置在 package.json
中的 workspaces
字段
workspaces
字段接收一个数组,数组里面可以填写相对根目录的文件夹名称或者是通配符,在 npm install
期间自动符号链接的这些包称为单个工作区。在以下示例中,位于文件夹 ./packages
内的所有文件夹都将被视为工作区,只要它们包含有效的 package.json
文件即可:
{
"name": "workspace-example",
"workspaces": [
"./packages/*"
]
}
运行 npm install
,./packages
内的所有包都将创建符号链接到当前目录的 node_modules
文件夹里,对于包的使用和查找,和正常安装的 npm
包相同。
使用 npm init 自动执行定义新工作区
例如,在已经定义了 package.json
的项目中运行:
npm init -w ./packages/a -y
如果不存在文件夹 ./packages/a
,此命令将创建文件夹 ./packages/a
和 ./packages/a/package.json
文件。同时,在当前目录的 package.json
中添加 workspaces
字段,和值 ["packages\\a"]
。
也可以一次添加多个工作区:
npm init -w packages/a -w packages/b -y
添加依赖到工作区
可以直接在工作区,进行安装、更新和删除依赖。例如,有下面的包结构:
.
+-- package.json
`-- packages
+-- a
| `-- package.json
`-- b
`-- package.json
如果要从 npm
库中添加一个名为 abbrev
的依赖到工作区 a
,可以使用以下命令告诉 npm
安装程序应该将包添加为工作区 a
的依赖项:
npm install abbrev -w a
如果要卸载 abbrev
的依赖,则要使用以下命令:
npm uninstall abbrev -w a
其他的 npm
命令也同样适用于工作区。
在工作区中运行命令
假如,当前项目中已经存在工作区 a
和 b
,可以在指定的工作区 a
中运行以下 npm
命令:
npm run test --workspace=a
这相当于以下步骤:
cd packages/a && npm run test
也可以指定多个参数:
npm run test --workspace=a --workspace=b
或者在所有配置的工作区中运行该命令,将按照它们在 package.json
中出现的顺序在每个工作区中运行:
npm run test --workspaces
如果使 npm
忽略缺少目标脚本的工作区,可以加上 --if-present
标志。
npm run test --workspaces --if-present
单独部署
只安装工作区 a
中的依赖包并运行:
npm install --production --workspace=a
npm run prod --workspace=a
示例
假如,当前项目中已经存在工作区 a
:
.
+-- node_modules
| `-- a -> ../packages/a
+-- package-lock.json
+-- package.json
`-- packages
+-- a
| `-- package.json
在工作区 a
中新建 index.js
文件,并加入以下内容:
module.exports = 'a'
在当前目录下,新建 lib 目录和 index.js
文件,并加入以下内容:
const moduleA = require('a')
console.log(moduleA) // -> a
然后运行 node lib/index.js
,可以看到工作区和正常安装的 npm
包使用并无差别。我们可以轻松地发布这些嵌套的工作空间以在其他地方使用。
和 monorepo 配合使用
如果维护一个单体仓库太过庞大了,维护多个仓库又太过繁琐,monorepo
的理念是维护一个仓库,但是一个仓库划分为多个 package
。
方式一:
各个子项目都集合到一个项目中来,package.json
只有一份在根目录,所有项目中的 npm
包都安装到根目录,在根目录的 package.json
中定义开发和部署子项目的命令。
缺点:
- 命令混乱;
- 无法应对子项目之间存在
npm
包冲突的问题;(比如,A
项目想用webpack4
,B
项目想用webpack5
)
方式二:
各个子项目都集合到一个项目中来,根目录和各个子包都各自有一份 package.json
,但基础的构建工具在根目录进行安装,比如上面提到的 webpack
、webpack-cli
、webpack-dev-server
、html-webpack-plugin
、webpack-merge
,全都在根目录进行安装,和业务相关的 npm
包都安装到各自子项目中。
缺点:
如果子项目中有依赖相同的包,不得不在各个子项目中重复安装;
同样无法应对子项目之间存在 npm
包冲突的问题;
如果某天想把 B
项目移除,成本很高;
方式三:
各个子项目都集合到一个项目中来,各个子包都各自有一份 package.json
,根目录无 package.json
。
缺点:
- 同样如果子项目中有依赖相同的包,不得不在各个子项目中重复安装;
使用 workspaces
就可以很好的解决了上面的所有问题!
其他应用
- 对于已经存在的项目而言,比如一个是
Web
的,一个是H5
的,他们属于同一个业务,所以有大量的代码可以复用,又因为只涉及这两个项目而已,把公共代码做成npm
包又有点太杀鸡用牛刀,而采用复制、粘贴的方式又显然是非常低效的,这时可以使用workspaces
将它们合并成一个项目。 mock
服务也是个子项目单独一套,但是大多数接口的数据都是可以公用的,只是url
前缀不同。这样就可以使用workspaces
将它们合并成一个项目,这是对原项目改动量最小的方案。