基于 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 中,通常有多个模块(包),例如可以将共享的工具函数、组件等提取为独立的包供其他项目使用。我们现在创建两个包,一个是共享库 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",这个要特别注意!!!
上面图片第一个错误就是没有配置"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 可以有效地组织和管理多个模块或项目,适合大型项目开发场景。