轻轻松松做一个基于 lerna-pnpm-workspace 的 monorepo 组件库项目

806 阅读4分钟

  一般情况下,我们的项目分为 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

1.png

2.7、测试依赖更新

  我们去改下 common 包里面那个sum函数,改成:

export const sum = (a, b) => a * 2 + b

  保存后,会发现数据更新了:

2.png

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)