Monorepo — Turborepo, Nx, Workspace Cho Codebase Lớn
Quản lý codebase nhiều package với monorepo: Turborepo, Nx, npm/pnpm workspaces và lợi ích của monorepo.
Monorepo là chiến lược đặt nhiều package/project trong cùng một repository. Tưởng phức tạp — nhưng với công cụ hiện đại, nó đơn giản hơn bạn nghĩ.
Monorepo vs Polyrepo#
| Monorepo | Polyrepo |
|---|---|
| Code chung 1 repo | Mỗi project 1 repo riêng |
| Dễ share code | Phải publish package |
| atomic commit xuyên package | Phải release nhiều repo |
| Tooling phức tạp hơn | Tooling đơn giản hơn |
| CI/CD 1 pipeline | CI/CD nhiều pipeline |
pnpm Workspaces — Nền Tảng#
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'yamlCấu trúc project:
my-monorepo/
├── apps/
│ ├── web/ # Next.js app
│ ├── api/ # Express API
│ └── admin/ # Admin dashboard
├── packages/
│ ├── ui/ # Shared UI components
│ ├── utils/ # Shared utilities
│ ├── config/ # ESLint, tsconfig
│ └── db/ # Prisma schema + client
├── pnpm-workspace.yaml
├── package.json # Root package.json
└── turbo.jsonplaintextRoot package.json#
{
"private": true,
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint",
"test": "turbo test"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.5.0"
}
}jsonTurborepo — Cache & Task Runner#
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], // Build dependencies trước
"outputs": ["dist/**", ".next/**"],
"cache": true
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.ts", "test/**/*.ts"]
}
}
}jsonTurborepo caches output — nếu code không đổi, build lại trong mili giây:
# Lần 1 — build thật
turbo build
# Tasks: 5 successful, 0 cached
# Lần 2 — dùng cache
turbo build
# Tasks: 0 successful, 5 cached → < 1sbashCấu Hình Package#
packages/ui/package.json#
{
"name": "@myorg/ui",
"private": true,
"exports": {
"./button": "./src/Button.tsx",
"./card": "./src/Card.tsx"
},
"peerDependencies": {
"react": "^18.0.0"
},
"devDependencies": {
"@myorg/config": "workspace:*",
"typescript": "^5.5.0"
}
}jsonapps/web/package.json#
{
"name": "web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*",
"@myorg/db": "workspace:*",
"next": "^14.0.0"
}
}jsonLocal Development#
# Chạy tất cả apps
pnpm dev
# Chạy specific app
pnpm --filter web dev
# Thêm dependency
pnpm --filter web add zod
# Thêm shared package vào app
pnpm --filter web add @myorg/ui
# Build specific package và dependent
pnpm --filter @myorg/ui buildbashDependency Graph#
web
/ | \
/ | \
/ | \
ui utils db
| |
+-------+---+
|
configplaintextTurborepo tự động xác định thứ tự build dựa trên dependency graph.
Shared Config#
packages/config/tsconfig.json#
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true
}
}jsonapps/web/tsconfig.json#
{
"extends": "@myorg/config/tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
"plugins": [{ "name": "next" }]
}
}jsonpackages/config/eslint.js#
module.exports = {
extends: ['turbo', 'prettier'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
},
};javascriptCI/CD với Monorepo#
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm turbo lint test build
# Cache cho Turborepo
- uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-yamlNx — Alternative Cho Turborepo#
Nx mạnh hơn Turborepo ở một số điểm:
// nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test"]
}
}
},
"generators": {
"@nx/react": {
"application": {
"style": "tailwind",
"bundler": "vite"
}
}
}
}typescriptNx có generator (code scaffolding), dependency graph visualization, và affected commands:
# Chỉ test package bị ảnh hưởng bởi thay đổi
nx affected:test
# Visualize dependency graph
nx graphbashDocker Build#
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/web/package.json apps/web/
COPY packages/ packages/
RUN pnpm install
# Build
COPY . .
RUN pnpm turbo build --filter=web
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/apps/web/.next ./.next
COPY --from=builder /app/apps/web/public ./public
COPY --from=builder /app/apps/web/package.json ./
EXPOSE 3000
CMD ["pnpm", "start"]dockerfileKết Luận#
Monorepo không dành cho mọi project — nếu team < 5 người, project nhỏ, polyrepo vẫn ổn. Nhưng khi bạn có:
- Nhiều app share code
- Nhiều package cần atomic change
- Team muốn reuse config, types, utils
Thì monorepo với pnpm + Turborepo là lựa chọn tuyệt vời. Bắt đầu từ đơn giản, thêm package khi cần.