基于 Monorepo 管理多个包(子项目)能够提高开发和管理效率,尤其是当项目包含多个模块或服务时。Monorepo 可以通过 pnpm、Yarn workspaces 等工具来实现多包管理,同时不同模块间可以共享代码。

下面我会详细讲解如何使用 pnpm 来配置一个简单的 Monorepo 项目,并演示如何在不同模块之间使用共享代码。


一、初始化 Monorepo 项目

首先,我们通过 pnpm 创建一个 Monorepo 项目,并设置 pnpm-workspace.yaml 进行工作空间配置。

初始化项目

mkdir mono01
cd mono01
pnpm init

创建 pnpm-workspace.yaml

在项目根目录下创建 pnpm-workspace.yaml,指定需要管理的工作空间路径:

packages:
  - 'packages/*'
  - 'apps/*'

这表示 pnpm 会管理 packages/ 和 apps/ 目录下的所有子项目。

Monorepo 管理多个包_缓存

Monorepo 管理多个包_json_02


二、创建多个包

在 Monorepo 中,通常有多个模块(包),例如可以将共享的工具函数、组件等提取为独立的包供其他项目使用。我们现在创建两个包,一个是共享库 packages/utils,另一个是应用项目 apps/app1

创建共享工具包

mkdir -p packages/utils
cd packages/utils
pnpm init

在 packages/utils 下创建一个工具函数文件:

// packages/utils/src/index.js
export function sayHello(name) {
  return `Hello, ${name}!`;
}

在 packages/utils/package.json 中确保添加以下配置,暴露入口文件:

{
  "name": "@my-org/utils",
  "version": "1.0.0",
  "main": "src/index.js"
}

创建应用包

mkdir -p apps/app1
cd apps/app1
pnpm init

在 apps/app1/package.json 中添加对共享包的依赖:

{
  "name": "@my-org/utils",
  "version": "1.0.0",
  "main": "src/index.js"
}

"workspace:*" 表示这个依赖项来自当前 Monorepo 的工作空间中的 packages/utils 包。


 三、配置包的依赖与互相调用

在 apps/app1 中,我们可以直接使用 @my-org/utils 包提供的工具函数。

创建一个简单的应用入口:

// apps/app1/src/index.js
import { sayHello } from '@my-org/utils';

console.log(sayHello('Monorepo'));


四、安装依赖并运行项目

回到项目根目录,运行以下命令:

pnpm install

这会安装所有包的依赖项,并且会自动链接 Monorepo 内部的包。

运行 apps/app1 中的脚本:

cd apps/app1
node src/index.js

你会看到输出:

Hello, Monorepo!

这里有个需要主要的地方:

就是apps/app1下的package.json中要配置  "type": "module",这个要特别注意!!!

Monorepo 管理多个包_缓存_03

上面图片第一个错误就是没有配置"type":"module"。


五、使用 Vite 构建应用

假设你想在 apps/app1 中创建一个基于 Vite 的 Vue 项目,并且使用 @my-org/utils 提供的工具包。

初始化 Vite 项目

在 apps/app1 中创建 Vite 项目:


cd apps/app1
pnpm create vite

选择 Vue 模板。接下来,安装依赖:

pnpm install

使用共享工具包

在 apps/app1/src/App.vue 中引入并使用工具函数:

<template>
  <div>{{ greeting }}</div>
</template>

<script setup>
import { ref } from 'vue';
import { sayHello } from '@my-org/utils';

const greeting = ref(sayHello('Vue with Monorepo'));
</script>

配置 vite.config.js 别名

为了能够让 Vite 正常识别并引入 @my-org/utils 包,可能需要在 vite.config.js 中配置路径别名:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@my-org/utils': path.resolve(__dirname, '../../packages/utils/src'),
    },
  },
});


六、Monorepo 中的测试与构建

使用 Vitest 测试工具包

你可以为 packages/utils 包添加单元测试。安装 Vitest:

cd packages/utils
pnpm add -D vitest

在 packages/utils/src/index.test.js 中编写测试:

import { describe, it, expect } from 'vitest';
import { sayHello } from './index';

describe('sayHello', () => {
  it('returns greeting message', () => {
    expect(sayHello('Test')).toBe('Hello, Test!');
  });
});

运行测试:

pnpm vitest

构建整个 Monorepo 项目

你可以通过 pnpm 的 workspace 机制一次性构建所有子项目:

pnpm run build --filter "apps/*"

这种方式可以确保按依赖关系先构建 packages/utils,然后构建 apps/app1


七、工作流优化(Hoisting 和缓存)

Hoisting 共享依赖

pnpm 默认会将共享的依赖包提升到顶层,减少重复安装的依赖项。你可以通过设置 .npmrc 文件来控制 Hoisting:

hoist = true

依赖缓存

pnpm 利用全局缓存机制提高安装速度。所有包会缓存到全局存储区域,减少网络下载和重复安装。



总结

  • Monorepo 基础配置:通过 pnpm-workspace.yaml 定义包管理路径。
  • 模块共享与复用:在 Monorepo 中,使用内部依赖 "workspace:*" 来共享和引用模块。
  • Vite 项目与共享模块集成:通过 vite.config.js 配置路径别名,使应用能够引入 Monorepo 内部的包。
  • 测试与构建:通过 pnpm 一次性构建和测试所有子项目。

这样,Monorepo 可以有效地组织和管理多个模块或项目,适合大型项目开发场景。