2801 字
14 分钟
Webpack Monorepo 完整指南:理论与实战
Webpack Monorepo 完整指南:理论与实战
本文档详细讲解如何使用 Webpack 构建和管理 Monorepo 项目, 包含完整的理论、配置方案、最佳实践和真实案例。
第一章:什么是 Monorepo?
1.1 Monorepo 的定义
Monorepo(单一仓库)是什么?
在一个 Git 仓库中管理多个相关的项目/包。
传统方式(多仓库 Multi-repo):
warehouse-ui/ ← 独立仓库 1
├── src/
└── package.json
warehouse-api/ ← 独立仓库 2
├── src/
└── package.json
warehouse-utils/ ← 独立仓库 3
├── src/
└── package.json
问题:
- 管理困难
- 依赖同步复杂
- 跨包共享代码麻烦
---
Monorepo 方式(单一仓库):
warehouse/ ← 一个仓库
├── packages/
│ ├── ui/ ← 包 1
│ ├── api/ ← 包 2
│ ├── utils/ ← 包 3
│ └── common/ ← 公共包
├── package.json
└── .git/
优势:
- 统一管理
- 共享代码容易
- 依赖版本一致
- 原子性更新
1.2 Monorepo 的核心优势
// 优势 1:代码共享
packages/utils/
└── src/
└── helpers.ts // 所有包都可以使用
packages/ui/
└── src/
└── Button.tsx
// 可以直接 import { helpers } from '@warehouse/utils'
// 优势 2:依赖版本一致
// 所有包共享 node_modules,保证版本一致
// 优势 3:原子性更新
// 多个包一起更新,不会出现不兼容
// 优势 4:统一构建和测试
// 可以一次性构建所有包
1.3 Monorepo 的应用场景
✅ 适合 Monorepo:
- 大型项目,由多个相关包组成
- UI 组件库 + 应用 + 文档
- 核心库 + 插件库 + 应用
- 前端 + 后端 + 共享工具
- 公司内部多个产品共享代码
❌ 不适合 Monorepo:
- 完全独立的项目
- 需要独立发版和版本控制
- 包之间无共享代码
- 团队很小,项目很简单
第二章:Monorepo 的实现方案
2.1 主流的 Monorepo 工具
// 方案 1:Yarn Workspaces(推荐,最简单)
// 优点:简单易用,官方支持
// 缺点:功能相对基础
// 方案 2:Npm Workspaces(Npm 7+)
// 优点:npm 原生支持,无需额外工具
// 缺点:功能不如 Yarn
// 方案 3:Lerna + Yarn Workspaces(推荐,功能完整)
// 优点:完整的 Monorepo 解决方案
// 缺点:配置稍复杂
// 方案 4:Nx(企业级,功能最强)
// 优点:功能完整,缓存,任务编排
// 缺点:学习成本高,配置复杂
// 方案 5:Turborepo(新兴,性能最好)
// 优点:性能超快,配置简单
// 缺点:相对较新,社区还在增长
本文档重点:Webpack + Yarn Workspaces + Lerna
2.2 Monorepo 的项目结构
warehouse/ # 根项目
├── packages/ # 所有包的目录
│ ├── @warehouse/ui/ # 包 1:UI 组件库
│ │ ├── src/
│ │ ├── dist/
│ │ ├── package.json
│ │ └── webpack.config.js
│ ├── @warehouse/api/ # 包 2:API 层
│ │ ├── src/
│ │ ├── dist/
│ │ ├── package.json
│ │ └── webpack.config.js
│ ├── @warehouse/utils/ # 包 3:工具函数
│ │ ├── src/
│ │ ├── dist/
│ │ ├── package.json
│ │ └── webpack.config.js
│ └── @warehouse/app/ # 包 4:主应用
│ ├── src/
│ ├── dist/
│ ├── package.json
│ └── webpack.config.js
├── .gitignore
├── .npmrc # npm 配置
├── lerna.json # Lerna 配置
├── package.json # 根项目配置
├── webpack.config.js # 共享的 webpack 配置
└── tsconfig.json # 共享的 TypeScript 配置
第三章:使用 Yarn Workspaces 构建 Monorepo
3.1 初始化项目
# 1. 创建项目目录
mkdir warehouse
cd warehouse
# 2. 初始化 npm
npm init -y
# 3. 配置 yarn workspaces
# 修改 package.json
3.2 根项目的 package.json
{
"name": "warehouse",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"devDependencies": {
"webpack": "^5.88.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"@babel/core": "^7.22.0",
"@babel/preset-env": "^7.22.0",
"@babel/preset-react": "^7.22.0",
"@babel/preset-typescript": "^7.22.0",
"babel-loader": "^9.1.2",
"typescript": "^5.0.0",
"lerna": "^6.0.0"
}
}
3.3 创建包结构
# 创建包目录
mkdir -p packages/@warehouse/ui
mkdir -p packages/@warehouse/api
mkdir -p packages/@warehouse/utils
mkdir -p packages/@warehouse/app
# 为每个包初始化 package.json
cd packages/@warehouse/ui
npm init -y
# 修改 package.json...
cd ../api
npm init -y
# 修改 package.json...
# 以此类推
3.4 各包的 package.json
packages/@warehouse/utils/package.json
{
"name": "@warehouse/utils",
"version": "1.0.0",
"description": "Shared utilities",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch"
},
"peerDependencies": {},
"devDependencies": {}
}
packages/@warehouse/ui/package.json
{
"name": "@warehouse/ui",
"version": "1.0.0",
"description": "UI Component Library",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch"
},
"dependencies": {
"@warehouse/utils": "1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
packages/@warehouse/app/package.json
{
"name": "@warehouse/app",
"version": "1.0.0",
"description": "Main Application",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"dependencies": {
"@warehouse/ui": "1.0.0",
"@warehouse/api": "1.0.0",
"@warehouse/utils": "1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
3.5 Webpack 配置
packages/@warehouse/utils/webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'babel-loader',
exclude: /node_modules/
}
]
},
externals: {
// 不打包 react,让使用者提供
react: 'react',
'react-dom': 'react-dom'
}
}
packages/@warehouse/ui/webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
alias: {
'@warehouse/utils': path.resolve(__dirname, '../utils/src')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
externals: {
react: 'react',
'react-dom': 'react-dom'
}
}
packages/@warehouse/app/webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js'
},
devServer: {
port: 3000,
open: true,
historyApiFallback: true
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
alias: {
'@warehouse/utils': path.resolve(__dirname, '../utils/src'),
'@warehouse/ui': path.resolve(__dirname, '../ui/src'),
'@warehouse/api': path.resolve(__dirname, '../api/src')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
第四章:使用 Lerna 管理 Monorepo
4.1 Lerna 是什么?
// Lerna 提供了:
// 1. 统一的版本管理
// 2. 自动化的发布流程
// 3. 依赖关系管理
// 4. 批量命令执行
// 虽然 Yarn Workspaces 可以处理 node_modules,
// 但 Lerna 提供了更高级的功能
4.2 初始化 Lerna
# 安装 Lerna(已经在根项目的 devDependencies 中)
npm install -D lerna
# 初始化 Lerna
npx lerna init
# 此时会生成 lerna.json
4.3 lerna.json 配置
{
"version": "1.0.0",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
],
"command": {
"publish": {
"ignoreChanges": [
"*.md"
],
"message": "chore(release): publish %s"
}
}
}
4.4 常用 Lerna 命令
# 列出所有包
lerna list
# 批量安装依赖
lerna bootstrap
# 在所有包中执行命令
lerna exec npm run build
# 运行 npm script
lerna run build
# 发布包
lerna publish
# 检查文件变化
lerna changed
# 清理所有 dist 目录
lerna exec -- rm -rf dist
4.5 Monorepo 的根项目 npm scripts
{
"scripts": {
"bootstrap": "lerna bootstrap",
"build": "lerna run build",
"dev": "lerna run dev --stream",
"test": "lerna run test",
"clean": "lerna exec -- rm -rf dist node_modules",
"publish": "lerna publish"
}
}
第五章:包之间的依赖管理
5.1 本地依赖 vs npm 依赖
// 方式 1:直接引用本地源代码(开发时)
// webpack.config.js 中使用 alias
resolve: {
alias: {
'@warehouse/utils': path.resolve(__dirname, '../utils/src')
}
}
// 方式 2:在 package.json 中声明依赖(生产时)
{
"dependencies": {
"@warehouse/utils": "1.0.0" // 指向本地包
}
}
// Yarn Workspaces 会自动链接本地包
5.2 处理循环依赖
// ❌ 循环依赖的情况(要避免)
// @warehouse/ui 依赖 @warehouse/utils
// @warehouse/utils 又依赖 @warehouse/ui
// → 循环依赖!
// 解决方案:重新设计架构
// @warehouse/common // 新建基础包
// ├─ types.ts
// └─ constants.ts
// @warehouse/utils // 工具函数
// └─ 依赖 @warehouse/common
// @warehouse/ui // UI 组件
// └─ 依赖 @warehouse/common 和 @warehouse/utils
// 这样就形成了清晰的依赖树,避免了循环依赖
5.3 共享配置
根项目下创建 shared-config 包
packages/
└── @warehouse/shared-config/
├── webpack.config.base.js # 共享的 webpack 配置
├── tsconfig.json # 共享的 TypeScript 配置
├── babel.config.js # 共享的 Babel 配置
└── package.json
packages/@warehouse/shared-config/webpack.config.base.js
const path = require('path')
module.exports = {
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
各包使用共享配置
// packages/@warehouse/ui/webpack.config.js
const baseConfig = require('@warehouse/shared-config/webpack.config.base')
const path = require('path')
const { merge } = require('webpack-merge')
module.exports = merge(baseConfig, {
mode: 'production',
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
}
})
第六章:构建流程
6.1 完整的构建流程
# 1. 安装依赖(一次性)
npm install
# 或
yarn install
# 2. 启动开发模式(监听所有包的变化)
npm run dev
# 3. 构建所有包
npm run build
# 4. 测试
npm run test
# 5. 发布
npm run publish
6.2 开发工作流
// 开发时的流程:
// 1. 修改 @warehouse/utils 的代码
packages/@warehouse/utils/src/helpers.ts
// ↓ 自动重新编译
// 2. 修改 @warehouse/ui 的代码
packages/@warehouse/ui/src/Button.tsx
// ↓ 自动重新编译
// 3. 修改 @warehouse/app 的代码
packages/@warehouse/app/src/App.tsx
// ↓ webpack-dev-server 热更新
// 所有修改立即反映到浏览器中
6.3 构建输出
warehouse/
└── packages/
├── @warehouse/utils/
│ └── dist/
│ ├── index.js # 已编译
│ └── index.d.ts # 类型定义
├── @warehouse/ui/
│ └── dist/
│ ├── index.js
│ └── index.d.ts
├── @warehouse/api/
│ └── dist/
│ ├── index.js
│ └── index.d.ts
└── @warehouse/app/
└── dist/
├── index.html
├── main.abc123de.js
└── ...
第七章:实战案例:完整的 UI 组件库 Monorepo
7.1 项目结构
component-library/
├── packages/
│ ├── @lib/core/ # 核心工具
│ ├── @lib/utils/ # 工具函数
│ ├── @lib/styles/ # 共享样式
│ ├── @lib/components/ # UI 组件
│ ├── @lib/docs/ # 文档站点
│ └── @lib/examples/ # 示例应用
├── lerna.json
├── package.json
└── README.md
7.2 核心包:@lib/components
packages/@lib/components/src/Button/index.tsx
import React from 'react'
import { getClassName } from '@lib/utils'
import styles from './Button.module.css'
interface ButtonProps {
variant?: 'primary' | 'secondary'
size?: 'small' | 'medium' | 'large'
children: React.ReactNode
onClick?: () => void
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
children,
onClick
}) => {
const className = getClassName(
styles.button,
styles[variant],
styles[size]
)
return (
<button className={className} onClick={onClick}>
{children}
</button>
)
}
packages/@lib/components/src/index.ts
export { Button } from './Button'
export { Input } from './Input'
export { Select } from './Select'
// ... 其他组件
7.3 工具包:@lib/utils
packages/@lib/utils/src/index.ts
export const getClassName = (...classes: (string | undefined)[]) => {
return classes.filter(Boolean).join(' ')
}
export const debounce = (fn: Function, delay: number) => {
let timeout: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeout)
timeout = setTimeout(() => fn(...args), delay)
}
}
export const throttle = (fn: Function, delay: number) => {
let lastTime = 0
return (...args: any[]) => {
const now = Date.now()
if (now - lastTime >= delay) {
fn(...args)
lastTime = now
}
}
}
7.4 文档应用:@lib/docs
packages/@lib/docs/src/App.tsx
import React from 'react'
import { Button, Input, Select } from '@lib/components'
export default function DocsApp() {
return (
<div>
<h1>Component Library</h1>
<section>
<h2>Button</h2>
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
</section>
<section>
<h2>Input</h2>
<Input placeholder="Enter text" />
</section>
</div>
)
}
第八章:最佳实践
8.1 包的命名规范
// ✅ 好的命名规范
@warehouse/ui // 公司名/功能名
@warehouse/api-client // 使用 kebab-case
@warehouse/utils // 简洁清晰
// ❌ 不好的命名
warehouse-ui // 没有作用域
WarehouseUI // 不用 PascalCase
warehouse_utils // 不用 snake_case
8.2 版本管理
# 固定版本(推荐)
# 所有包版本相同,一起发布
lerna publish --exact
# 独立版本
# 每个包可以有不同的版本
lerna publish --independent
8.3 CI/CD 集成
# .github/workflows/build.yml
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
- run: npm run build
- run: npm run test
8.4 常见问题解决
// 问题 1:依赖没有被正确链接
// 解决方案:
yarn install --force
# 或
lerna bootstrap --force-local
// 问题 2:循环依赖
// 解决方案:重新设计架构,避免循环引用
// 问题 3:TypeScript 路径别名不工作
// 解决方案:配置 tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@warehouse/*": ["./packages/@warehouse/*/src"]
}
}
}
第九章:Monorepo 的优化和性能
9.1 构建缓存
// webpack.config.js
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
}
9.2 增量构建
# 只构建改变的包
lerna run build --since origin/main
# 构建特定包
lerna run build --scope @warehouse/ui
9.3 并行执行
# 默认:顺序执行
lerna run build
# 并行执行(最多 4 个并发)
lerna run build --concurrency 4
第十章:总结
核心要点
- Monorepo 是什么:在一个仓库中管理多个相关的包
- 为什么用 Monorepo:代码共享、依赖一致、原子更新
- 如何实现:Yarn Workspaces + Lerna
- 关键配置:webpack alias、package.json dependencies、tsconfig paths
- 最佳实践:清晰的依赖图、避免循环依赖、共享配置
学习路线
1. 理解 Monorepo 概念 ✓
2. 初始化 Yarn Workspaces ✓
3. 配置 Webpack 为每个包 ✓
4. 设置 Lerna 版本管理 ✓
5. 实施最佳实践 ✓
何时使用 Monorepo
✅ 适合:
- UI 组件库 + 应用 + 文档
- 核心库 + 多个应用
- 相关的多个项目
❌ 不适合:
- 完全独立的项目
- 需要独立版本控制
- 很小的项目
现在你已经掌握了 Webpack Monorepo 的完整知识! 🎉
Monorepo 是大型项目的强大工具,可以大大提升开发效率。
接下来我们继续学习 Vite 吧! 🚀
Webpack Monorepo 完整指南:理论与实战
https://fuwari.vercel.app/posts/webpack-monorepo-完整指南理论与实战/
