Use when setting up monorepo tooling, optimizing builds, or migrating between tools with Turborepo, Nx, Bazel, Lerna for efficient task running, caching, and code generation.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
This skill provides comprehensive guidance on monorepo build systems, task runners, package managers, and development tools that enable efficient development, building, and testing across multiple packages in a monorepo.
High-performance build system with intelligent caching and task orchestration.
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
".env",
"tsconfig.json"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
".next/**",
"build/**"
],
"cache": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"cache": false
}
},
"globalEnv": [
"NODE_ENV",
"CI"
]
}
{
"remoteCache": {
"enabled": true
}
}
With Vercel:
# Link to Vercel for remote caching
turbo login
turbo link
With custom cache:
{
"remoteCache": {
"enabled": true,
"signature": true,
"preflight": true
}
}
Usage:
# Run build across all packages
turbo run build
# Run with filter
turbo run build --filter=@myorg/web
# Run with dependencies
turbo run build --filter=@myorg/web...
# Force rebuild (skip cache)
turbo run build --force
# Dry run
turbo run build --dry-run
# Prune for deployment
turbo prune --scope=@myorg/web
Extensible build system with powerful code generation and analysis.
{
"extends": "nx/presets/npm.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"test",
"lint"
],
"parallel": 3,
"cacheDirectory": "node_modules/.cache/nx"
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"],
"cache": true
},
"test": {
"inputs": [
"default",
"^production"
],
"cache": true
}
},
"namedInputs": {
"default": [
"{projectRoot}/**/*"
],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts"
]
}
}
{
"name": "web",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/web",
"main": "apps/web/src/main.ts",
"tsConfig": "apps/web/tsconfig.app.json"
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "web:build"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/web"],
"options": {
"jestConfig": "apps/web/jest.config.ts"
}
}
}
}
{
"nxCloudAccessToken": "YOUR_ACCESS_TOKEN",
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "YOUR_ACCESS_TOKEN"
}
}
}
}
Usage:
# Run target on all projects
nx run-many --target=build --all
# Run on affected projects only
nx affected --target=test --base=main
# View dependency graph
nx graph
# Generate new library
nx generate @nx/js:library my-lib
# Run task on specific project
nx build web
# Clear cache
nx reset
Google's scalable build system for very large monorepos.
workspace(name = "my_workspace")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Load Node.js rules
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "...",
urls = ["https://github.com/bazelbuild/rules_nodejs/..."],
)
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories")
node_repositories(
node_version = "18.16.0",
package_manager = "pnpm",
)
# packages/ui/BUILD.bazel
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
load("@npm//@bazel/typescript:index.bzl", "ts_library")
ts_library(
name = "ui",
srcs = glob(["src/**/*.ts", "src/**/*.tsx"]),
deps = [
"@npm//react",
"@npm//react-dom",
"@npm//@types/react",
],
visibility = ["//visibility:public"],
)
pkg_npm(
name = "ui_pkg",
deps = [":ui"],
package_name = "@myorg/ui",
substitutions = {
"0.0.0-PLACEHOLDER": "{STABLE_VERSION}",
},
)
Usage:
# Build target
bazel build //packages/ui:ui
# Build all targets in package
bazel build //packages/ui/...
# Test target
bazel test //packages/ui:ui_test
# Run target
bazel run //apps/web:serve
# Clean builds
bazel clean
# Query dependency graph
bazel query 'deps(//packages/ui:ui)'
Multi-package repository management and publishing tool.
{
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true,
"packages": [
"packages/*",
"apps/*"
],
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish",
"ignoreChanges": [
"**/__tests__/**",
"**/*.md"
]
},
"version": {
"allowBranch": ["main", "next"],
"message": "chore(release): version packages"
},
"bootstrap": {
"npmClientArgs": ["--no-package-lock"]
}
}
}
Usage:
# Bootstrap packages
lerna bootstrap
# Run command in all packages
lerna run build
# Run command in changed packages
lerna run test --since origin/main
# Publish packages
lerna publish
# Publish from git tags
lerna publish from-git
# Version packages
lerna version
# List packages
lerna list
Scalable monorepo manager with strict dependency management.
{
"rushVersion": "5.108.0",
"pnpmVersion": "8.10.0",
"nodeSupportedVersionRange": ">=18.0.0",
"projectFolderMinDepth": 1,
"projectFolderMaxDepth": 2,
"projects": [
{
"packageName": "@myorg/web",
"projectFolder": "apps/web",
"reviewCategory": "production"
},
{
"packageName": "@myorg/ui",
"projectFolder": "packages/ui",
"reviewCategory": "production"
}
]
}
Rush build configuration:
{
"operationSettings": [
{
"operationName": "build",
"outputFolderNames": ["dist", "lib"]
}
]
}
Usage:
# Install dependencies
rush install
# Update dependencies
rush update
# Build all projects
rush build
# Build changed projects
rush rebuild
# Custom commands
rush my-command
# Publish packages
rush publish
Execute tasks across packages in parallel for speed.
Turborepo parallel execution:
# Auto-detects parallelism
turbo run build
# Limit concurrency
turbo run build --concurrency=4
# No limit
turbo run build --concurrency=100
Nx parallel execution:
# Default parallel (3)
nx run-many --target=build --all
# Custom parallel
nx run-many --target=build --all --parallel=5
# Max parallel
nx run-many --target=build --all --parallel=false
PNPM parallel execution:
# Run in all packages (parallel)
pnpm -r run build
# Sequential execution
pnpm -r --workspace-concurrency=1 run build
# Custom concurrency
pnpm -r --workspace-concurrency=4 run build
Define which tasks must complete before others.
{
"pipeline": {
"build": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
},
"deploy": {
"dependsOn": ["build", "test", "lint"]
}
}
}
Dependency types:
^build - Build dependencies first (topological)build - Current package build first["^build", "lint"] - Multiple dependenciesRun tasks on specific packages or changed packages only.
Turborepo filtering:
# Single package
turbo run build --filter=@myorg/web
# Package and dependencies
turbo run build --filter=@myorg/web...
# Package and dependents
turbo run build --filter=...@myorg/web
# Multiple packages
turbo run build --filter=@myorg/web --filter=@myorg/api
# Changed packages
turbo run build --filter=[HEAD^1]
Nx affected:
# Affected by current changes
nx affected --target=build
# Affected between commits
nx affected --target=test --base=main --head=HEAD
# Affected files
nx affected:apps
nx affected:libs
PNPM filtering:
# Single package
pnpm --filter @myorg/web run build
# Package and dependencies
pnpm --filter @myorg/web... run build
# Package and dependents
pnpm --filter ...@myorg/web run build
# Changed packages
pnpm --filter "[main]" run test
Automatically rebuild on file changes.
{
"scripts": {
"dev": "turbo run dev --parallel",
"dev:web": "turbo run dev --filter=@myorg/web..."
}
}
With concurrently:
{
"scripts": {
"dev": "concurrently \"pnpm:dev:*\"",
"dev:web": "pnpm --filter @myorg/web run dev",
"dev:api": "pnpm --filter @myorg/api run dev"
}
}
With Turborepo watch:
# Watch mode for development
turbo run dev --parallel --no-cache
Cache task outputs locally for faster rebuilds.
Turborepo local cache:
{
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**"],
"cache": true
}
}
}
Cache location: node_modules/.cache/turbo
Nx local cache:
{
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["build", "test", "lint"],
"cacheDirectory": "node_modules/.cache/nx"
}
}
}
}
Cache location: node_modules/.cache/nx
Share cache across team members and CI environments.
Turborepo Remote Cache with Vercel:
# Login to Vercel
turbo login
# Link repository
turbo link
# Enable remote caching (automatic)
turbo run build
Nx Cloud:
# Connect to Nx Cloud
nx connect-to-nx-cloud
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "YOUR_TOKEN"
}
}
}
}
Custom Remote Cache:
// turbo-remote-cache.ts
import { createServer } from 'turbo-remote-cache';
createServer({
storage: {
type: 's3',
bucket: 'my-turbo-cache',
region: 'us-east-1'
},
port: 3000
});
Control what invalidates the cache.
Turborepo cache keys:
{
"globalDependencies": [
".env",
"tsconfig.json",
".eslintrc.js"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"inputs": [
"src/**/*.ts",
"src/**/*.tsx",
"package.json"
],
"outputs": ["dist/**"]
}
}
}
Nx cache inputs:
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/**/*.test.ts"
],
"sharedGlobals": [
"{workspaceRoot}/tsconfig.base.json",
"{workspaceRoot}/.eslintrc.json"
]
},
"targetDefaults": {
"build": {
"inputs": ["production", "^production", "sharedGlobals"]
}
}
}
Cache invalidation:
# Clear all caches
turbo run build --force
# Clear Nx cache
nx reset
# Skip cache for single run
nx build web --skip-nx-cache
Optimize Docker builds in monorepo context.
# Dockerfile for web app
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Copy root package files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY .npmrc ./
# Copy package files for dependencies
COPY packages/ui/package.json ./packages/ui/
COPY packages/utils/package.json ./packages/utils/
COPY apps/web/package.json ./apps/web/
# Install dependencies
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Build stage
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build with Turbo
RUN pnpm turbo run build --filter=@myorg/web...
# Production stage
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/apps/web/.next ./apps/web/.next
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "apps/web/.next/server.js"]
Create custom code generators for consistency.
// tools/generators/library/index.ts
import {
Tree,
formatFiles,
generateFiles,
joinPathFragments
} from '@nx/devkit';
export interface LibraryGeneratorSchema {
name: string;
directory: string;
}
export default async function (tree: Tree, schema: LibraryGeneratorSchema) {
const projectRoot = joinPathFragments('packages', schema.directory);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
projectRoot,
{
...schema,
tmpl: ''
}
);
await formatFiles(tree);
}
Template files:
// tools/generators/library/files/src/index.ts__tmpl__
export function <%= name %>() {
return '<%= name %>';
}
// tools/generators/library/files/package.json__tmpl__
{
"name": "@myorg/<%= name %>",
"version": "0.0.1"
}
Usage:
nx generate @myorg/tools:library --name=my-lib --directory=shared
Use Plop for simpler code generation.
// plopfile.js
export default function (plop) {
plop.setGenerator('package', {
description: 'Create a new package',
prompts: [
{
type: 'input',
name: 'name',
message: 'Package name:'
},
{
type: 'list',
name: 'type',
message: 'Package type:',
choices: ['library', 'app', 'service']
}
],
actions: [
{
type: 'addMany',
destination: 'packages/{{name}}',
templateFiles: 'templates/package/**/*',
base: 'templates/package'
}
]
});
}
Templates:
// templates/package/package.json
{
"name": "@myorg/{{name}}",
"version": "0.0.1",
"type": "{{type}}"
}
Usage:
pnpm plop package
Automate new package creation.
#!/bin/bash
# scripts/create-package.sh
NAME=$1
TYPE=$2
if [ -z "$NAME" ]; then
echo "Usage: create-package.sh <name> <type>"
exit 1
fi
DIR="packages/$NAME"
# Create directory structure
mkdir -p "$DIR/src"
mkdir -p "$DIR/__tests__"
# Create package.json
cat > "$DIR/package.json" << EOF
{
"name": "@myorg/$NAME",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"test": "vitest"
}
}
EOF
# Create initial files
echo "export const $NAME = '$NAME';" > "$DIR/src/index.ts"
# Create README
cat > "$DIR/README.md" << EOF
# @myorg/$NAME
Description of $NAME package.
EOF
echo "Created package: @myorg/$NAME"
Maintain consistency with shared templates.
// scripts/new-component.ts
import fs from 'fs';
import path from 'path';
interface ComponentOptions {
name: string;
package: string;
}
function createComponent({ name, package: pkg }: ComponentOptions) {
const dir = path.join('packages', pkg, 'src', 'components', name);
fs.mkdirSync(dir, { recursive: true });
// Component file
fs.writeFileSync(
path.join(dir, `${name}.tsx`),
`import React from 'react';
export interface ${name}Props {
children?: React.ReactNode;
}
export function ${name}({ children }: ${name}Props) {
return <div>{children}</div>;
}
`
);
// Test file
fs.writeFileSync(
path.join(dir, `${name}.test.tsx`),
`import { render } from '@testing-library/react';
import { ${name} } from './${name}';
describe('${name}', () => {
it('renders children', () => {
const { getByText } = render(<${name}>Hello</${name}>);
expect(getByText('Hello')).toBeInTheDocument();
});
});
`
);
// Index file
fs.writeFileSync(
path.join(dir, 'index.ts'),
`export { ${name} } from './${name}';
export type { ${name}Props } from './${name}';
`
);
}
Identify which packages changed based on Git history.
Turborepo:
# Changed since last commit
turbo run build --filter=[HEAD^1]
# Changed in last 3 commits
turbo run build --filter=[HEAD^3]
# Changed between branches
turbo run build --filter=[origin/main...HEAD]
Nx:
# Affected since main branch
nx affected --target=build --base=main
# Affected between specific commits
nx affected --target=test --base=abc123 --head=def456
# Show affected projects
nx affected:apps
nx affected:libs
Understand project relationships for smarter builds.
Visualize with Nx:
# Full dependency graph
nx graph
# Affected dependency graph
nx affected:graph
# Specific project graph
nx graph --focus=web
Query with Nx:
# Show dependencies of project
nx show project web --web
# List all projects
nx show projects
Turborepo graph:
# Generate task graph
turbo run build --graph
# Output to file
turbo run build --graph=graph.html
Only rebuild what's necessary based on changes.
Nx affected strategy:
{
"affected": {
"defaultBase": "main"
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}
Turborepo affected strategy:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
CI configuration:
# .github/workflows/ci.yml
- name: Build affected
run: |
turbo run build --filter=[origin/main...HEAD]
Build only changed files within packages.
TypeScript incremental builds:
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo"
}
}
Webpack incremental builds:
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
};
Next.js incremental builds:
// next.config.js
module.exports = {
experimental: {
incrementalCacheHandlerPath: './cache-handler.js'
}
};
Fast, disk-efficient package manager with strict dependency model.
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'services/*'
# .npmrc
shared-workspace-lockfile=true
link-workspace-packages=true
prefer-workspace-packages=true
strict-peer-dependencies=false
auto-install-peers=true
save-workspace-protocol=rolling
Key advantages:
Commands:
# Install all workspace dependencies
pnpm install
# Add dependency to package
pnpm --filter @myorg/web add react
# Add workspace dependency
pnpm --filter @myorg/web add @myorg/ui
# Update dependencies
pnpm --filter @myorg/web update react
# Run script in all packages
pnpm -r run build
# Run in changed packages
pnpm --filter "[main]" run test
Yarn's workspace implementation with plugin ecosystem.
Yarn Classic:
{
"workspaces": [
"packages/*",
"apps/*"
]
}
Yarn Berry (v2+):
# .yarnrc.yml
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.4.cjs
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
Key advantages:
Commands:
# Install dependencies
yarn install
# Add dependency
yarn workspace @myorg/web add react
# Run script in workspace
yarn workspace @myorg/web run build
# Run in all workspaces
yarn workspaces foreach run build
# Run in changed workspaces (Berry)
yarn workspaces foreach --since=main run test
Native NPM workspace support (v7+).
{
"workspaces": [
"packages/*",
"apps/*"
]
}
Key advantages:
Commands:
# Install dependencies
npm install
# Add dependency to workspace
npm install react --workspace=@myorg/web
# Run script in workspace
npm run build --workspace=@myorg/web
# Run in all workspaces
npm run build --workspaces
# Run in specific workspaces
npm run test --workspaces --if-present
Performance comparison (1000 packages, cold install):
Migration from NPM to PNPM:
# Remove node_modules and package-lock.json
rm -rf node_modules package-lock.json
# Install PNPM
corepack enable pnpm
# Create workspace file
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF
# Install with PNPM
pnpm install
# Update package.json scripts
# Replace "npm" with "pnpm"
Migration from Yarn to PNPM:
# Remove node_modules and yarn.lock
rm -rf node_modules yarn.lock
# Create workspace file (convert from package.json)
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF
# Install with PNPM
pnpm install --lockfile-only
pnpm install
Leverage local and remote caching for maximum speed.
Implementation:
Share build artifacts across CI runs and developers.
Implementation:
Only run tasks on changed packages.
Implementation:
Design efficient task dependency graphs.
Implementation:
Automate package and component creation.
Implementation:
Commit lock files for reproducible builds.
Implementation:
--frozen-lockfile in CIMaintain clear documentation for all tools.
Implementation:
Track and optimize build times.
Implementation:
Stay current with monorepo tool versions.
Implementation:
Ensure workspace is correctly configured.
Implementation:
Missing significant performance gains.
Solution: Enable local and remote caching, configure outputs correctly.
Inefficient task dependencies and ordering.
Solution: Minimize dependencies, enable parallelism, visualize pipeline.
Not using affected analysis.
Solution: Implement affected commands, configure base branch, test locally.
Wasting CI time on unchanged packages.
Solution: Use affected in CI, configure correctly, monitor savings.
Inconsistent package structure.
Solution: Create generators, document usage, enforce in reviews.
Different team members using different commands.
Solution: Document standard commands, use package.json scripts, code review.
Stale builds from incorrect cache configuration.
Solution: Configure inputs/outputs correctly, test cache behavior, monitor.
Complex task graphs that are hard to maintain.
Solution: Simplify dependencies, regular reviews, documentation.
Using multiple tools that overlap.
Solution: Choose one primary tool, justify additions, regular audits.
Not tracking build performance over time.
Solution: Implement metrics, regular reviews, set budgets, optimize.
Apply monorepo tooling practices when: