一般情况下,我们的项目分为 mono-repo 和 multi-repo。
mono-repo是一个仓库里存放多个项目,multi-repo是一个仓库只存放一个项目。我们想要做一个组件库,里面可能会有图标库、文档库、组件库等不同类型的库。这样就非常适合mono-repo了,组件库修改了,文档库就能立刻更新,而不必等到依赖的仓库发个版本才能查看效果。
听起来很简单,做起来也 ... 简单!
1、技术选型
这里,我选择了pnpm + workspace。
和 yarn 、 npm 相比,pnpm安装速度快,它会缓存安装过的包,因此再次安装时不必再去下载了,直接简简单单地创建一个软连接就好了。更重要的是,pnpm很能省空间,mac 256gb用户狂喜,从此迎来了春天,当然这也是因为pnpm通过store统一管理所有缓存过的库实现的。
workspace 是 npm v7.x 后在npm-cli中新增的功能,该功能支持从顶级根目录中管理本地文件系统中的多个包。
Lerna,一个管理工具,用于管理包含多个软件包的JavaScript项目。
当然,除了pnpm外,yarn、npm也是支持workspace的。但是,得益于pnpm速度快、省空间的特性,个人还是推荐pnpm配合workspace食用为佳。
2、开始干活
2.1、创建目录 my-monorepo
mkdir my-monorepo
2.2、安装全局包
npm i -g lerna
2.3、初始化项目
lerna init
2.4、支持 pnpm workspace
要想 lerna 支持 pnpm workspace, 你需要做以下几步:
- lerna.json文件中,启用 "useWorkspaces": true、"npmClient": "pnpm"。
- 新建 pnpm-workspace.yaml,内容如下:
// pnpm-workspace.yaml packages: - "packages/*"
此时,项目目录结构如下:
my-monorepo
├─ .gitignore
├─ lerna.json
├─ package.json
├─ packages
└─ pnpm-workspace.yaml
2.5、创建函数包 common
cd ./packages && mkdir common
cd ./common && npm init -y
这时,我们在/packages/common文件夹下新建index.js文件,然后作为库的入口文件。然后,新建index.d.ts,作为入口类型文件。
// index.js
export const sum = (a, b) => a + b
// index.d.ts
export declare function sum (a: number, b: number): number;
此时,莫要忘记了:
要改package.json!改package.json!package.json!
// package.json
{
"name": "@monorepo/common",
"version": "1.0.0",
"description": "",
"type": "module",
"types": "./index.d.ts",
"module": "./index.js",
"private": false,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
想要在npm上发布一个库,我们先要拥有一个名字对吧!我们想要这是一个私有库,就加个前缀@。这里,为了提醒是Monorepo的库,我就取名 @monorepo/common。
如果我们想要别人能使用我们函数包里面的 sum 函数,该怎么办呢?就像一些流行库lodash那样,“import {debounce} from 'lodash';”。这就需要一个入口文件,所以我们得配置:
"type": "module"
"types": "./index.d.ts"
"module": "./index.js"
tips:type里可选 module 或 commonjs,代表引入该库是按哪种模块化规范来处理。module时使用import 引入,commonjs时使用 require引入(前端模块化规范ES、CMD可以参考其他资料)。types 表示类型文件。module是ES规范的入口文件地址。还有个main字段,这里没有使用,对应commonjs规范的入口文件地址,使用require进行引用。
现在,如果我们在根目录下,执行 lerna ls,就可以看到打印出:
lerna notice cli v6.5.1
@monorepo/common
lerna success found 1 packages
2.6、创建测试包 dev
让我们来到 /packages目录下,运行命令:
pnpm create vite
文件名输入 dev,选择React + TypeScript项目,然后:
cd dev && pnpm install
等安装好依赖,请不要忘记改下package.json名称!这里,我们取名@monorepo/dev吧。接下来给这个项目引入 common 包的依赖。使用 :
pnpm -F @monorepo/dev add @monorepo/common
小提示: 【pnpm -F xxx add yyy】即 pnpm 过滤。pnpm -F 等价于 pnpm --filter。其中,xxx 表示当前选中要操作的包,yyy 表示将要被安装的包。
经过上面我的一番操作后,package.json文件长这样:
// package.json
{
"name": "@monorepo/dev",
"private": false,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@monorepo/common": "workspace:^1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^4.9.3",
"vite": "^4.1.0"
}
}
安装依赖成功,下面我们去试试看。修改 ./src/main.tsx 文件:
// ./src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { sum } from "@monorepo/common";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<div>{sum(1, 2)}</div>
</React.StrictMode>
);
启动项目:pnpm dev
2.7、测试依赖更新
我们去改下 common 包里面那个sum函数,改成:
export const sum = (a, b) => a * 2 + b
保存后,会发现数据更新了:
2.8、使用lerna
到这里,一个monorepo项目其实已经完成了。我们使用lerna能更方便在根目录下管理包。就像下面这样:
- lerna run dev --scope @monorepo/dev(运行 @monorepo/dev 的dev脚本 )
- lerna run build (运行所有子包的build脚本)
- lerna clean -y && rimraf ./node_modules (删除项目中所有node_modules)