使用 pnpm workspace 实现 monorepo 策略

359

什么是Monorepo?

Monorepo 将许多不同项目集中在一个代码仓库中进行管理,这些项目在某种程度上相互依赖,但在逻辑上是独立的,这意味着不同的团队可以独立地进行工作。 与之相对的是 Multirepo,每个项目都拥有单独的代码仓库。

Monorepo 的优点:

  1. 代码共享和复用:在单一代码库中,不同项目和模块可以轻松地共享和复用代码,降低了重复开发的成本。
  2. 依赖管理:Monorepo 可以使开发人员更容易地管理项目间的依赖关系,减少了版本冲突和升级问题。
  3. 原子提交:Monorepo 允许开发人员在一个提交中更新多个项目或模块,这有助于保持代码的一致性。
  4. 更简洁的工作流程:使用 Monorepo 可以简化构建、测试和部署等工作流程,提高开发效率。

Monorepo 的缺点:

  1. 代码库规模:随着项目和代码的增长,Monorepo 的规模可能变得庞大,从而影响性能和存储需求。
  2. 权限管理:在一个大型代码库中管理访问权限可能变得复杂,特别是在多团队协作的情况下。
  3. 潜在的耦合:由于代码位于同一仓库中,可能导致不同项目之间的耦合过于紧密,影响项目的独立性和灵活性。

一些知名的开源项目,如 Next.jsViteElement Plus,都采用了 Monorepo 策略。在选择 Monorepo 之前,需要权衡其优缺点,并考虑它是否适用于特定的开发环境和需求。

monorepo 方案实践

monorepo可以使用 lerna,也可以使用更简洁的 yarn、pnpm,但是 pnpm 相对于 yarn 包管理机制更加强大完善,所以我们采用 pnpm 实现 monorepo。

创建项目

首先,全局安装pnpm,创建项目目录,然后使用 pnpm init命令创建一个新的空项目。

npm i -g pnpm 
mkdir xxxx-monorepo
cd ./xxxx-monorepo
pnpm init

创建 pnpm Workspaces

根目录下必须有 pnpm-workspace.yaml 文件,它定义了工作空间的根目录

packages:
  - packages/*

创建业务项目

使用 pnpm create vite命令在 packages 目录下创建project1项目

mkdir packages
cd packages
pnpm create vite project1 --template react
cd project1
pnpm install
pnpm dev

创建使用公共 utils

在 packages 目录下创建公共 utils 目录

mkdir utils

添加 package.json 文件

{
  "name": "@xxxx/utils",
  "main": "index.js",
  "version": "0.0.1"
}

创建 log.js 文件

const log = (text) => {
  console.log(text);
};

export default log;

创建 index.js 入口文件,并导出 log 方法

import log from './log.js';
export { log };

进入 packages/project1,添加 @xxxx/utils 依赖

cd ../packages/project1
pnpm add @xxxx/utils

在 App.jsx 中引用 log 方法

import { log } from '@xxxx/utils';

function App() {
  return (
    <div className="App">
      <button
        onClick={() => {
          log('onClick');
        }}
      ></button>
    </div>
  );
}

export default App;

创建使用公共组件

在 packages 目录下创建 components 目录

mkdir packages/components
cd packages/components

添加 package.json 文件

{
  "name": "@xxxx/components",
  "main": "index.js",
  "version": "0.0.1"
}

创建 Button 组件

packages/
  ├── components/
  │   └── Button/
  │       └───index.tsx
  ├── utils/
  └── project1/

在 index.tsx 文件中编写 Button 组件的代码。

export default function Button(props) {
  return <button style={{ background: 'red' }} {...props} />;
}

创建 index.js 入口文件,并导出 Button 组件

import Button from './Button';
export { Button };

进入 packages/project1,添加 @xxxx/components 依赖

cd ../packages/project1
pnpm add @xxxx/components

在 App.jsx 中引用 Button 组件

import { log } from '@xxxx/utils'
import { Button } from '@xxxx/components';

function App() {
  return (
    <div className="App">
      <Button
        onClick={() => {
          log('onClick');
        }}
      ></Button>
    </div>
  );
}

export default App;

项目地址

pnpm-monorepo-demo - StackBlitz