292 lines
6.5 KiB
Markdown
292 lines
6.5 KiB
Markdown
|
|
# Turborepo Monorepo Best Practices — MeshiTrack
|
||
|
|
|
||
|
|
> Instruction file for managing the monorepo workspace.
|
||
|
|
|
||
|
|
## Workspace Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
MeshiTrack/
|
||
|
|
├── packages/
|
||
|
|
│ ├── api/ # NestJS backend → @meshitrack/api
|
||
|
|
│ ├── web/ # Next.js frontend → @meshitrack/web
|
||
|
|
│ └── shared/ # Shared types & DTOs → @meshitrack/shared
|
||
|
|
├── docker/ # Docker configs (not a package)
|
||
|
|
├── docs/ # Documentation (not a package)
|
||
|
|
├── turbo.json # Turborepo pipeline config
|
||
|
|
├── package.json # Root workspace config
|
||
|
|
├── tsconfig.base.json
|
||
|
|
├── .eslintrc.js
|
||
|
|
├── .prettierrc
|
||
|
|
└── .gitignore
|
||
|
|
```
|
||
|
|
|
||
|
|
## Root package.json
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"name": "meshitrack",
|
||
|
|
"private": true,
|
||
|
|
"workspaces": ["packages/*"],
|
||
|
|
"scripts": {
|
||
|
|
"dev": "turbo run dev",
|
||
|
|
"build": "turbo run build",
|
||
|
|
"lint": "turbo run lint",
|
||
|
|
"test": "turbo run test",
|
||
|
|
"typecheck": "turbo run typecheck",
|
||
|
|
"clean": "turbo run clean"
|
||
|
|
},
|
||
|
|
"devDependencies": {
|
||
|
|
"turbo": "^2.x",
|
||
|
|
"typescript": "^5.x",
|
||
|
|
"eslint": "^9.x",
|
||
|
|
"prettier": "^3.x"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## turbo.json Configuration
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"$schema": "https://turbo.build/schema.json",
|
||
|
|
"globalDependencies": ["**/.env.*local"],
|
||
|
|
"tasks": {
|
||
|
|
"build": {
|
||
|
|
"dependsOn": ["^build"],
|
||
|
|
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||
|
|
},
|
||
|
|
"dev": {
|
||
|
|
"dependsOn": ["^build"],
|
||
|
|
"cache": false,
|
||
|
|
"persistent": true
|
||
|
|
},
|
||
|
|
"lint": {
|
||
|
|
"dependsOn": ["^build"]
|
||
|
|
},
|
||
|
|
"test": {
|
||
|
|
"dependsOn": ["^build"]
|
||
|
|
},
|
||
|
|
"typecheck": {
|
||
|
|
"dependsOn": ["^build"]
|
||
|
|
},
|
||
|
|
"clean": {
|
||
|
|
"cache": false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Key concepts
|
||
|
|
|
||
|
|
- **`dependsOn: ["^build"]`**: Before running a task in a package, first build all its dependencies. This ensures `shared` is built before `api` or `web` run.
|
||
|
|
- **`outputs`**: What Turborepo caches. If outputs haven't changed, Turborepo replays from cache.
|
||
|
|
- **`persistent: true`**: For long-running dev servers that shouldn't be cached.
|
||
|
|
- **`cache: false`**: Disables caching for tasks that should always run.
|
||
|
|
|
||
|
|
## Package Dependencies
|
||
|
|
|
||
|
|
### Shared package is the foundation
|
||
|
|
|
||
|
|
```
|
||
|
|
@meshitrack/shared ← @meshitrack/api
|
||
|
|
← @meshitrack/web
|
||
|
|
```
|
||
|
|
|
||
|
|
Both `api` and `web` depend on `shared`, but never on each other.
|
||
|
|
|
||
|
|
### Reference shared in package.json
|
||
|
|
|
||
|
|
```json
|
||
|
|
// packages/api/package.json
|
||
|
|
{
|
||
|
|
"name": "@meshitrack/api",
|
||
|
|
"dependencies": {
|
||
|
|
"@meshitrack/shared": "workspace:*"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// packages/web/package.json
|
||
|
|
{
|
||
|
|
"name": "@meshitrack/web",
|
||
|
|
"dependencies": {
|
||
|
|
"@meshitrack/shared": "workspace:*"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Import from shared
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// In packages/api or packages/web
|
||
|
|
import { Product, NutritionInfo, ProductCategory } from '@meshitrack/shared';
|
||
|
|
import { CreateProductSchema } from '@meshitrack/shared/validation';
|
||
|
|
```
|
||
|
|
|
||
|
|
## Shared Package Setup
|
||
|
|
|
||
|
|
### packages/shared/package.json
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"name": "@meshitrack/shared",
|
||
|
|
"version": "0.0.1",
|
||
|
|
"private": true,
|
||
|
|
"main": "./dist/index.js",
|
||
|
|
"types": "./dist/index.d.ts",
|
||
|
|
"exports": {
|
||
|
|
".": {
|
||
|
|
"types": "./dist/index.d.ts",
|
||
|
|
"default": "./dist/index.js"
|
||
|
|
},
|
||
|
|
"./validation": {
|
||
|
|
"types": "./dist/validation/index.d.ts",
|
||
|
|
"default": "./dist/validation/index.js"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"scripts": {
|
||
|
|
"build": "tsc",
|
||
|
|
"dev": "tsc --watch",
|
||
|
|
"clean": "rm -rf dist",
|
||
|
|
"typecheck": "tsc --noEmit"
|
||
|
|
},
|
||
|
|
"dependencies": {
|
||
|
|
"zod": "^3.x"
|
||
|
|
},
|
||
|
|
"devDependencies": {
|
||
|
|
"typescript": "^5.x"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### packages/shared/tsconfig.json
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"extends": "../../tsconfig.base.json",
|
||
|
|
"compilerOptions": {
|
||
|
|
"outDir": "./dist",
|
||
|
|
"rootDir": "./src",
|
||
|
|
"declaration": true,
|
||
|
|
"declarationMap": true,
|
||
|
|
"sourceMap": true
|
||
|
|
},
|
||
|
|
"include": ["src/**/*"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Shared package must be platform-agnostic
|
||
|
|
|
||
|
|
Rules for code in `packages/shared`:
|
||
|
|
|
||
|
|
- **No** Node.js imports (`fs`, `path`, `http`, etc.)
|
||
|
|
- **No** NestJS decorators or imports
|
||
|
|
- **No** Next.js imports
|
||
|
|
- **No** DOM/browser APIs
|
||
|
|
- Only pure TypeScript: types, interfaces, enums, Zod schemas, utility functions
|
||
|
|
|
||
|
|
## TypeScript Configuration
|
||
|
|
|
||
|
|
### Root tsconfig.base.json
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"compilerOptions": {
|
||
|
|
"target": "ES2022",
|
||
|
|
"lib": ["ES2022"],
|
||
|
|
"module": "commonjs",
|
||
|
|
"moduleResolution": "node",
|
||
|
|
"strict": true,
|
||
|
|
"esModuleInterop": true,
|
||
|
|
"skipLibCheck": true,
|
||
|
|
"forceConsistentCasingInFileNames": true,
|
||
|
|
"resolveJsonModule": true,
|
||
|
|
"declaration": true,
|
||
|
|
"declarationMap": true,
|
||
|
|
"sourceMap": true,
|
||
|
|
"incremental": true
|
||
|
|
},
|
||
|
|
"exclude": ["node_modules", "dist"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Each package extends this and overrides as needed (e.g., `web` uses `"jsx": "preserve"`, `"module": "esnext"`).
|
||
|
|
|
||
|
|
## Development Workflow
|
||
|
|
|
||
|
|
### Start all packages in dev mode
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npm run dev
|
||
|
|
# Turborepo runs: shared (build) → then api (dev) + web (dev) in parallel
|
||
|
|
```
|
||
|
|
|
||
|
|
### Build all packages
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npm run build
|
||
|
|
# Turborepo builds shared first, then api and web in parallel
|
||
|
|
```
|
||
|
|
|
||
|
|
### Run tasks for a specific package
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npx turbo run dev --filter=@meshitrack/api
|
||
|
|
npx turbo run test --filter=@meshitrack/web
|
||
|
|
```
|
||
|
|
|
||
|
|
### Add a dependency to a specific package
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd packages/api
|
||
|
|
npm install @nestjs/schedule
|
||
|
|
# Or from root:
|
||
|
|
npm install @nestjs/schedule --workspace=packages/api
|
||
|
|
```
|
||
|
|
|
||
|
|
## Caching
|
||
|
|
|
||
|
|
### Turborepo remote caching (optional)
|
||
|
|
|
||
|
|
For CI, consider enabling remote caching to share build cache across machines:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npx turbo login
|
||
|
|
npx turbo link
|
||
|
|
```
|
||
|
|
|
||
|
|
Or use a self-hosted cache server for full self-hosted setup.
|
||
|
|
|
||
|
|
### Local caching
|
||
|
|
|
||
|
|
Turborepo caches locally in `node_modules/.cache/turbo` by default. It's fast and requires no setup.
|
||
|
|
|
||
|
|
## CI Integration
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# .github/workflows/ci.yml
|
||
|
|
name: CI
|
||
|
|
on: [push, pull_request]
|
||
|
|
|
||
|
|
jobs:
|
||
|
|
build:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: actions/setup-node@v4
|
||
|
|
with:
|
||
|
|
node-version: 20
|
||
|
|
cache: 'npm'
|
||
|
|
- run: npm ci
|
||
|
|
- run: npx turbo run build lint test typecheck
|
||
|
|
```
|
||
|
|
|
||
|
|
## Common Pitfalls
|
||
|
|
|
||
|
|
1. **Forgetting to build shared**: If `api` or `web` can't find types from `shared`, run `npm run build` from root. The `dependsOn: ["^build"]` config handles this in Turborepo tasks.
|
||
|
|
|
||
|
|
2. **Circular dependencies**: Never import from `api` in `web` or vice versa. Only import from `shared`.
|
||
|
|
|
||
|
|
3. **Version mismatches**: Keep TypeScript versions aligned across all packages. Pin in root `devDependencies`.
|
||
|
|
|
||
|
|
4. **Large `node_modules`**: Use `npm` workspaces hoisting. Most dependencies are installed at root level. Only package-specific versions go in package-level `node_modules`.
|