pnpm创建monorepo项目

  目录

使用pnpm工具创建monorepo项目

monorepo项目是当下非常流行的,例如React、Vue、Vite等项目都在使用。创建monorepo项目的方法很多,本文使用pnpm来创建。

全局安装pnpm

1
npm install -g pnpm

创建文件夹

创建一个叫monorepo的文件夹

1
mkdir monorepo

创建相关文件

初始化package.json

1
pnpm init

pnpm-workspace.yaml

新建pnpm-workspace.yaml文件,内容如下

1
2
packages:
- 'packages/*'

表示packages下是子项目

.npmignore

1
node_modules/

这个文件设置发布的时候过滤掉node_modules文件夹的内容

创建packages文件夹

在packages下创建子项目

1
mkdir core utils api

创建好三个子项目文件夹后,分别初始化package.json,之后需要修改package.json的name,加上npmjs上所对应的Organizations名字(名字前加@)。
再加上"publishConfig": {"access": "public"},用来告诉npmjs这个是公开的(私有是收费的,发布的时候会报错)。
core package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "@nux-monorepo/core",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node ./index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@nux-monorepo/api": "workspace:^"
}
}

api package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "@nux-monorepo/api",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@nux-monorepo/utils": "workspace:^"
}
}

utils package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "@nux-monorepo/utils",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"publishConfig": {
"access": "public"
}
}

添加依赖

公共依赖

在monorepo目录下

1
pnpm add loadsh -w

-w表示在主项目目录下添加依赖

局部依赖

1
pnpm add @nux-monorepo/utils -F @nux-monorepo/api

表示给@nux-monorepo/api子项目添加了一个@nux-monorepo/utils的依赖。
再加一个依赖

1
pnpm add @nux-monorepo/api -F @nux-monorepo/core

可以看上边的package.json文件,添加依赖后的效果。
上边公共依赖和局部依赖执行命令都是在项目的根目录下执行即可。

测试

可以在子项目中运行npm run dev去执行,也可以在主项目下直接执行,在主项目的package.json下添加

1
"dev:core": "pnpm -F @nux-monorepo/core dev"

运行结果跟在子项目中是一样的。

发布

普通发布

  • 在npmjs官网有一个账号
  • 在npmjs新建一个Organizations,这个名字就是子项目package.json中name的前缀,@nux-monorepo/core中的nux-monorepo
  • 进入到子项目,执行pnpm publish,注意,这里需要把npm的源设置成官网的https://registry.npmjs.org/(因为npmjs的官方源下载速度慢,平时都会切换国内的镜像地址,这里需要注意下)

每次发布时需要修改version版本号,一样的话发布会失败。

脚本发布

上面的发布需要进入到每个子项目进行发布,非常麻烦,可以编写一个脚本,进行批量发布。
这里还有一点,可以在脚本中修改版本号,可以根据不同的规则修改,本文简单起见,把子项目中的version版本号都设置成主项目里的version,但是发布前主项目的version需要手动修改一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');

const packagesRoot = './packages';
const packageName = 'package.json';
let version;
// 获取主项目package.json中的version
let packageData = fs.readFileSync(path.resolve('./', packageName), 'utf8');
version = JSON.parse(packageData).version;
// 发布的命令行执行函数
function publishPackage(packageDir) {
exec(`cd ${packageDir} && pnpm publish`, (error, stdout, stderr) => {
if (error) {
console.error(`Error publishing ${packageDir}:`, error);
} else {
console.log(`Published: ${packageDir}`);
}
});
}
// 扫描子项目函数
function scanAndPublish(directory) {
fs.readdir(directory, { withFileTypes: true }, (err, files) => {
if (err) {
console.error('Error reading directory:', err);
return;
}

files.forEach((file) => {
if (file.isDirectory()) {
// 修改package.json中的version
let packagePath = path.resolve('./', packagesRoot, file.name, packageName);
let packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
packageData.version = version;
let writeData = JSON.stringify(packageData, null, 2);
fs.writeFileSync(packagePath, writeData, 'utf8');
// 发布
publishPackage(path.dirname(packagePath));
}
});
});
}

// 主入口
scanAndPublish(packagesRoot);

使用

1
npm i @nux-monorepo/core

相关代码

source