build-tooling
Skill from vincentbollaert/turborepo-monorepo
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- GitHub stars: 2
- Updated: 2025-11-21
Download the latest version (v1.0.0) from this page.
SKILL.md
# Build Tooling & Configuration
**Auto-detection:** Turborepo configuration, ESLint 9 flat config, Prettier, Husky pre-commit hooks, lint-staged, Bun, monorepo setup, syncpack
**When to use:**
- Configuring Turborepo task pipeline and caching strategies
- Setting up ESLint 9 flat config with shared configurations
- Configuring Husky pre-commit hooks with lint-staged
- Synchronizing dependencies across workspace with syncpack
**Key patterns covered:**
- Turborepo 2.4.2 with Bun 1.2.2 for monorepo orchestration
- ESLint 9 flat config with only-warn plugin (errors become warnings for better DX)
- Shared configurations (@repo/eslint-config, @repo/prettier-config, @repo/typescript-config)
- Husky + lint-staged for pre-commit quality gates (fast, staged files only)
---
# Build & Tooling
> **Quick Guide:** Turborepo 2.4.2 with Bun for monorepo orchestration. ESLint 9 flat config with `only-warn` plugin. Prettier shared config. Husky + lint-staged for pre-commit hooks. Shared TypeScript configs. Bun 1.2.2 package manager (3-10x faster installs).
---
## Turborepo Configuration
**ACTUAL IMPLEMENTATION: Turborepo 2.4.2 with Bun 1.2.2**
**Location:** `turbo.json` in repository root
### Task Pipeline
**Pattern:** Define task dependencies and caching behavior
**Key concepts:**
- `dependsOn: ["^build"]` - Run dependency tasks first (topological order)
- `outputs` - Define what files to cache
- `inputs` - Specify which files trigger cache invalidation
- `cache: false` - Disable caching for tasks with side effects
- `persistent: true` - Keep dev servers running
**ACTUAL tasks in this codebase:**
```json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": ["DATABASE_URL", "NODE_ENV"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULTquot;, "src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
},
"dev": {
"cache": false,
"persistent": true
},
"generate": {
"dependsOn": ["^generate"],
"cache": false
},
"lint": {}
}
}
```
### Caching Strategies
**What gets cached:**
- Build outputs (`dist/`, `.next/`)
- Test results (when `cache: true`)
- Lint results
**What doesn't get cached:**
- Dev servers (`cache: false`)
- Code generation (`cache: false` - generates files)
- Tasks with side effects
**Cache invalidation triggers:**
- Source file changes
- Dependency changes
- Environment variable changes (when in `env` array)
- Global dependencies changes (`.env`, `tsconfig.json`)
### Remote Caching
**Optional but recommended for teams:**
```json
{
"remoteCache": {
"signature": true
}
}
```
**Setup:** Link Vercel account, set `TURBO_TOKEN` and `TURBO_TEAM` env vars. Shares cache across team and CI.
### Environment Variables
**Pattern:** Declare environment variables in `env` array
```json
{
"tasks": {
"build": {
"env": ["DATABASE_URL", "NODE_ENV", "NEXT_PUBLIC_API_URL"]
}
}
}
```
**Why:** Turbo includes env vars in cache key - different values trigger rebuilds
---
## Linting Configuration
**ACTUAL IMPLEMENTATION: ESLint 9 with flat config**
**Location:** `packages/eslint-config/`
### Shared Config Pattern
**Pattern:** Shared base config extended by apps and packages
**Structure:**
```
packages/eslint-config/
├── base.js # Base config for all packages
├── react.js # React-specific rules
├── next.js # Next.js-specific rules
└── package.json
```
**Base config includes:** Recommended JS/TS rules, Prettier conflict resolution, Turbo env var validation, `only-warn` plugin (converts errors to warnings), and ignore patterns for build outputs.
### Key Features
**ESLint 9 flat config:**
- New configuration format (replaces `.eslintrc`)
- Simpler, more flexible
- Better TypeScript support
**Plugins used:**
- `@typescript-eslint` - TypeScript linting
- `eslint-config-prettier` - Disable conflicting Prettier rules
- `eslint-plugin-turbo` - Validate environment variables
- `eslint-plugin-only-warn` - Convert errors to warnings (better DX)
**Why `only-warn`:**
- Developers aren't blocked by errors
- Issues are visible but not disruptive
- CI can still fail on warnings if needed
### ESLint vs Biome (2025)
**Current choice: ESLint 9**
- Mature ecosystem
- Extensive plugin support
- Good TypeScript integration
**Biome (emerging alternative):**
- ✅ Extremely fast (Rust-based)
- ✅ Combined linter + formatter
- ✅ Drop-in Prettier replacement
- ❌ Smaller ecosystem
- ❌ Fewer plugins
**Recommendation:**
- Stick with ESLint for now (mature, stable)
- Monitor Biome adoption (may switch in 2025-2026)
- Consider Biome for greenfield projects
---
## Formatting
**ACTUAL IMPLEMENTATION: Prettier with shared config**
**Location:** `packages/prettier-config/`
### Shared Configuration
**Pattern:** Centralized Prettier config shared across all packages
**Key settings:** 100 char line width, 2-space indentation, double quotes, required semicolons, trailing commas everywhere, always use arrow parens.
**Usage in packages:**
```json
{
"prettier": "@repo/prettier-config"
}
```
### Format Settings Explained
**Line width:** `100` characters
- Balances readability and screen width
- Works well with side-by-side diffs
- Alternative: `120` for wider screens
**Quotes:** Double quotes (`"`)
- Consistent with JSON
- Fewer escapes in JSX
- Alternative: Single quotes (`'`) for smaller bundles
**Semicolons:** Required
- Explicit statement termination
- Avoids ASI issues
- Alternative: No semicolons (modern style)
**Trailing commas:** All
- Cleaner git diffs
- Easier to reorder items
- Required for multi-line
**Arrow parens:** Always
- Consistent with multi-param functions
- Easier to add params later
### Editor Integration
**VS Code:** Install Prettier extension
```json
// .vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
```
**EditorConfig:** Basic formatting rules
```ini
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js,jsx,ts,tsx,json}]
indent_style = space
indent_size = 2
```
### Prettier vs Biome
**Current choice: Prettier**
- Industry standard
- Extensive language support
- Battle-tested
**Biome alternative:**
- ~20x faster than Prettier
- Combined linter + formatter
- Compatible with Prettier config
- Growing adoption
**When to switch:**
- Large monorepos (speed matters)
- Greenfield projects
- When Biome plugin ecosystem matures
---
## Pre-commit Hooks
**ACTUAL IMPLEMENTATION: Husky + lint-staged**
**Location:** `.husky/` directory, per-package `lint-staged.config.mjs`
### Husky Configuration
**Pre-commit hook:**
```bash
# .husky/pre-commit
bunx lint-staged
```
**Pre-push hook:**
```bash
# .husky/pre-push
cd apps/client-react && bun run test --watch=false
```
### lint-staged Setup
**Pattern:** Per-package configuration runs ESLint with auto-fix on staged TypeScript and SCSS files.
**What runs on commit:**
- ESLint with auto-fix
- Prettier formatting (via ESLint integration)
- Only on staged files (fast!)
### What to Run Pre-commit
**Recommended (fast):**
- ✅ Linting with auto-fix
- ✅ Formatting
- ✅ Type checking (--noEmit, fast)
**NOT recommended (slow):**
- ❌ Full test suite (run in pre-push or CI)
- ❌ Full build (too slow)
- ❌ E2E tests (run in CI)
### Advanced Patterns
**Run affected tests only:**
```javascript
// lint-staged.config.mjs
export default {
"*.{ts,tsx}": (filenames) => {
const testFiles = filenames.filter((f) => f.includes(".test.")).map((f) => `bun test ${f}`);
return ["eslint --fix", ...testFiles];
},
};
```
**Type check only changed files:**
```javascript
export default {
"*.{ts,tsx}": ["eslint --fix", () => "tsc --noEmit --incremental"],
};
```
---
## TypeScript Configuration
**ACTUAL IMPLEMENTATION: Shared configs in `@repo/typescript-config`**
**Location:** `packages/typescript-config/`
### Shared Config Pattern
**Structure:**
```
packages/typescript-config/
├── base.json # Common settings for all packages
├── nextjs.json # Next.js-specific settings
├── react-library.json # React library settings
├── node.json # Node.js server settings
└── package.json
```
**Usage:**
```json
// apps/client-next/tsconfig.json
{
"extends": "@repo/typescript-config/nextjs.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
```
### Base Configuration
**Recommended settings:**
```json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"incremental": true,
"moduleResolution": "bundler"
}
}
```
### Path Aliases
**Pattern:** Configure in both tsconfig and build tool
```json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@lib/*": ["./src/lib/*"]
}
}
}
```
**Benefits:**
- Clean imports: `import { Button } from "@components/button"`
- No relative path hell: No `../../../components`
- Easy refactoring
---
## Package Manager
**ACTUAL IMPLEMENTATION: Bun 1.2.2**
**Location:** `package.json` (root)
```json
{
"packageManager": "bun@1.2.2"
}
```
### Why Bun?
**Benefits:**
- ⚡ Extremely fast installs (3-10x faster than npm/yarn)
- 🔧 Native TypeScript support
- 🎯 Compatible with npm packages
- 📦 Built-in test runner
- 🔨 Built-in bundler
**Alternatives:**
- **npm** - Slowest, but universal
- **yarn** - Faster, PnP mode
- **pnpm** - Efficient disk usage, fast
- **bun** - Fastest, modern
### Workspaces
**Pattern:** Monorepo with workspaces
```json
{
"workspaces": ["apps/*", "packages/*"]
}
```
**Benefits:**
- Shared dependencies across packages
- Hoist common dependencies
- Link local packages automatically
---
## Dependency Synchronization
**ACTUAL IMPLEMENTATION: Syncpack**
**Purpose:** Ensure consistent versions across workspace
**Commands:**
```json
{
"scripts": {
"deps:check": "syncpack list-mismatches",
"deps:fix": "syncpack fix-mismatches"
}
}
```
### Why Syncpack?
**Problem:**
- Different packages use different versions of React
- `@types/react` version mismatch
- Duplicate dependencies in bundle
**Solution:**
- Syncpack identifies version mismatches
- Auto-fix to use consistent versions
- Reduces bundle size
**Example:**
```bash
$ bun run deps:check
❌ react: 18.2.0, 18.3.0, 19.0.0 (3 versions!)
❌ @types/react: 18.2.0, 18.3.0 (2 versions!)
$ bun run deps:fix
✅ Updated react to 19.0.0 across all packages
✅ Updated @types/react to 18.3.0 across all packages
```
---
## Build Optimization
**Recommended practices for production builds**
### Bundle Analysis
**Tools:**
- `rollup-plugin-visualizer` - Vite/Rollup
- `@next/bundle-analyzer` - Next.js
- `webpack-bundle-analyzer` - Webpack
**Setup:**
```bash
bun add -D rollup-plugin-visualizer
bun run build --analyze
```
### Code Splitting
**Patterns:**
- Route-based splitting (automatic in Next.js)
- Component lazy loading with `React.lazy()`
- Vendor chunk splitting
- Dynamic imports for large libraries
### Tree Shaking
**Requirements:**
- ES modules (not CommonJS)
- Named exports (not default)
- Side-effect-free code
**Mark as side-effect-free:**
```json
{
"sideEffects": false
}
```
Or specify files with side effects:
```json
{
"sideEffects": ["*.css", "*.scss"]
}
```
### Performance Budgets
**Set size limits:**
```json
{
"build": {
"chunkSizeWarningLimit": 500,
"rollupOptions": {
"output": {
"manualChunks": {
"vendor": ["react", "react-dom"]
}
}
}
}
}
```
**Monitor:**
- Initial bundle < 200KB (gzip)
- Largest chunk < 500KB
- Total bundle < 1MB
---
# Build & Tooling - Examples
---
## Turborepo/Nx/Vite Configuration
### Example: Vite Configuration with Path Aliases
```typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@components": path.resolve(__dirname, "./src/components"),
"@features": path.resolve(__dirname, "./src/features"),
"@lib": path.resolve(__dirname, "./src/lib"),
"@hooks": path.resolve(__dirname, "./src/hooks"),
"@types": path.resolve(__dirname, "./src/types"),
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
// Vendor chunks
"react-vendor": ["react", "react-dom", "react-router-dom"],
"query-vendor": ["@tanstack/react-query"],
"ui-vendor": ["@radix-ui/react-dialog", "@radix-ui/react-dropdown-menu"],
},
},
},
},
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
});
```
```json
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@features/*": ["./src/features/*"],
"@lib/*": ["./src/lib/*"],
"@hooks/*": ["./src/hooks/*"],
"@types/*": ["./src/types/*"]
}
}
}
```
**Why:** Clean imports. Better organization. Vendor chunk splitting. API proxy for development.
**Edge Cases:**
- Use `@/` as primary alias, others as convenience
- Sync with tsconfig paths
- Consider dynamic imports for large features
---
### Example: Environment-Specific Builds
```typescript
// vite.config.ts
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
return {
plugins: [react()],
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
build: {
sourcemap: mode === "development",
minify: mode === "production",
rollupOptions: {
output: {
manualChunks:
mode === "production"
? {
"react-vendor": ["react", "react-dom"],
}
: undefined,
},
},
},
};
});
```
```json
// package.json
{
"scripts": {
"dev": "vite --mode development",
"build:staging": "vite build --mode staging",
"build:prod": "vite build --mode production"
}
}
```
```
# .env files
.env.development # Development settings
.env.staging # Staging settings
.env.production # Production settings
```
**Why:** Environment-specific optimizations. Conditional features. Different API endpoints.
**Edge Cases:**
- Never commit .env files
- Use .env.example as template
- Validate required env vars at build time
---
### Example: Incremental Builds with Turborepo Caching
```typescript
// turbo.json - Advanced caching configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
".env",
"tsconfig.json",
".eslintrc.js"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"cache": true,
"inputs": ["src/**/*.ts", "src/**/*.tsx", "**/*.test.ts", "**/*.test.tsx"]
},
"lint": {
"cache": true,
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
},
"remoteCache": {
"signature": true
}
}
```
```bash
# Local development - uses local cache
npx turbo run build
# ✅ Cache miss - Building...
# ✅ Packages built: 5
# ✅ Time: 45.2s
# Second run - hits cache
npx turbo run build
# ✅ Cache hit - Skipping...
# ✅ Packages restored: 5
# ✅ Time: 1.2s (97% faster)
# Only rebuilds changed packages
# Edit packages/ui/src/Button.tsx
npx turbo run build
# ✅ Cache hit: @repo/types, @repo/config, @repo/api-client
# ✅ Cache miss: @repo/ui (changed)
# ✅ Cache miss: web, admin (depend on @repo/ui)
# ✅ Time: 12.4s (73% faster)
```
```yaml
# .github/workflows/ci.yml - Remote caching in CI
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Needed for --filter
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: pnpm install
# Remote cache with Vercel
- name: Build
run: npx turbo run build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# Only run affected tests on PRs
- name: Test affected
if: github.event_name == 'pull_request'
run: npx turbo run test --filter=...[HEAD^]
# Run all tests on main
- name: Test all
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: npx turbo run test
```
```json
// package.json - Remote cache setup
{
"scripts": {
"build": "turbo run build",
"build:fresh": "turbo run build --force",
"build:affected": "turbo run build --filter=...[HEAD^1]",
"test:affected": "turbo run test --filter=...[HEAD^1]"
}
}
```
**Why:** Incremental builds save massive time. Local cache makes rebuilds instant. Remote cache shares across CI and developers. Only affected packages rebuild.
**Edge Cases:**
- Use `--force` to ignore cache when needed
- Set `globalDependencies` for files that affect all packages
- Use `inputs` to fine-tune what triggers cache invalidation
- Remote cache requires Vercel account or self-hosted solution
**Metrics:**
- First build: ~45s (5 packages)
- Cached build: ~1s (97% faster)
- Affected build: ~12s (73% faster)
- Team savings: Hours per week with remote cache
---
## Linting Configuration
### Example: ACTUAL ESLint 9 Flat Config
```javascript
// packages/eslint-config/base.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
import turboPlugin from "eslint-plugin-turbo";
import { plugin as onlyWarn } from "eslint-plugin-only-warn";
export const baseConfig = [
// ✅ Recommended base rules
js.configs.recommended,
// ✅ Disable Prettier conflicts
eslintConfigPrettier,
// ✅ TypeScript rules
...tseslint.configs.recommended,
// ✅ Turbo plugin for env var validation
{
plugins: {
turbo: turboPlugin,
},
rules: {
"turbo/no-undeclared-env-vars": "warn",
},
},
// ✅ Convert all errors to warnings
{
plugins: {
onlyWarn,
},
},
// ✅ Ignore build outputs
{
ignores: ["dist/**", "generated/**", ".next/**"],
},
];
```
```javascript
// packages/eslint-config/react.js
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import { baseConfig } from "./base.js";
export const reactConfig = [
...baseConfig,
// ✅ React plugin
{
plugins: {
react,
"react-hooks": reactHooks,
"jsx-a11y": jsxA11y,
},
settings: {
react: {
version: "detect",
},
},
rules: {
// React rules
"react/react-in-jsx-scope": "off", // Not needed in React 19
"react/prop-types": "off", // Using TypeScript
"react/no-unescaped-entities": "warn",
// Hooks rules
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive