Welcome to the Nuru WASM monorepo! This guide will help you understand our transition to a Turborepo-powered monorepo and how to work effectively within it.
- What is a Monorepo?
- Why Turborepo?
- Repository Structure
- Understanding pnpm Workspaces
- Working with Turborepo
- Managing Dependencies
- Running Tasks
- Caching & Performance
- Code Generation with
turbo gen - Best Practices
- Common Commands
- Troubleshooting
A monorepo (mono-repository) is a single repository that contains multiple distinct projects or packages. Instead of having separate repositories for your web app, WASM package, and shared utilities, everything lives together in one place.
- ✅ Single source of truth - All code in one place
- ✅ Easier code sharing - Reuse packages across apps
- ✅ Atomic commits - Change multiple packages in one commit
- ✅ Simplified dependency management - Shared dependencies across packages
- ✅ Better tooling - Unified CI/CD, linting, testing
Turborepo is a high-performance build system for JavaScript/TypeScript monorepos. It provides:
- Incremental builds - Only rebuild what changed
- Smart caching - Never do the same work twice
- Parallel execution - Run tasks across packages simultaneously
- Simple configuration - Single
turbo.jsonfile - Task pipelines - Define task dependencies clearly
- Remote caching - Share cache across team and CI (optional)
- Framework agnostic - Works with any JavaScript tooling
- Package manager agnostic - Works with npm, yarn, pnpm, bun
Our monorepo follows Turborepo's recommended conventions:
nuru-wasm/
├── apps/ # Deployable applications
│ └── nuru-svelte/ # SvelteKit web application
│ ├── src/
│ ├── static/
│ ├── package.json
│ └── vite.config.js
├── packages/ # Shared, reusable packages
│ └── nuru-wasm/ # Go WASM interpreter package
│ ├── main.go
│ ├── go.mod
│ └── package.json
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # pnpm workspace configuration
├── package.json # Root package.json (monorepo scripts)
└── pnpm-lock.yaml # Unified lockfile
| Directory | Purpose | Examples |
|---|---|---|
apps/ |
Deployable applications that end users interact with | Web apps, mobile apps, desktop apps |
packages/ |
Shared libraries and utilities used by apps | UI components, utilities, WASM modules |
Workspaces are a feature of package managers (pnpm, npm, yarn) that allow you to manage multiple packages in one repository. Each folder with a package.json inside apps/ or packages/ is treated as a workspace package.
In our monorepo:
nuru-svelteis a package (name:"nuru-svelte")nuru-wasmis a package (name:"@nuru/wasm")
-
Each workspace can have its own dependencies
// apps/nuru-svelte/package.json { "dependencies": { "@nuru/wasm": "workspace:*", "svelte": "^5.0.0" } }
-
Packages reference each other using
workspace:*protocol- This tells pnpm to link to the local workspace package
- No need to publish to npm for local development
-
pnpm creates symlinks between workspace packages
- Changes to
packages/nuru-wasmare immediately available inapps/nuru-svelte
- Changes to
-
Shared dependencies are hoisted to the root when possible
- Saves disk space
- Ensures version consistency
Our pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"This tells pnpm to treat all directories in apps/ and packages/ as workspace packages.
Our turbo.json defines the task pipeline - how tasks relate to each other and what they cache:
{
"$schema": "https://turborepo.com/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
".next/**",
"!.next/cache/**",
".svelte-kit/**",
"build/**",
"public/build/**",
"main.wasm",
"main",
"static/*.wasm"
]
},
"test": {
"dependsOn": ["^test"],
"cache": false
},
"dev": {
"persistent": true,
"cache": false
},
"lint": {
"outputs": []
}
}
}Defines task dependencies. The ^ symbol means "dependencies in upstream packages":
"build": {
"dependsOn": ["^build"]
}This means: "Before building this package, build all packages it depends on first."
Files/directories to cache when the task succeeds. The beauty of Turborepo is that you can define a superset of outputs. Turborepo will only cache what actually exists.
For example:
- Next.js outputs to
.next/** - Vite/SvelteKit outputs to
dist/**or.svelte-kit/** - WASM builds output to
main.wasmorstatic/*.wasm - General Node apps might output to
build/**
You list all possible build artifact directories, and Turborepo caches only what exists for each workspace.
Controls whether task outputs are cached:
true(default) - Cache outputs for reusefalse- Never cache (useful for dev servers, tests)
Marks long-running tasks (like dev servers) that shouldn't block other tasks:
"dev": {
"persistent": true,
"cache": false
}Following Turborepo's dependency management guidelines:
Install packages directly in the workspace that needs them:
# ✅ Good - Install in specific workspace
cd apps/nuru-svelte
pnpm add svelte
# Or from root with filter
pnpm add svelte --filter nuru-svelte# ❌ Bad - Don't install app dependencies in root
pnpm add svelteWhy?
- Improved clarity - Easy to see what each package depends on
- Enhanced flexibility - Different packages can use different versions
- Better caching - Fewer unnecessary cache misses
- Pruning - Turborepo can remove unused dependencies for Docker
The root package.json should only contain:
- Repository management tools -
turbo,husky,lint-staged - NOT application dependencies
// ✅ Good - Root package.json
{
"devDependencies": {
"turbo": "^2.7.1"
}
}// ❌ Bad - Don't put app deps in root
{
"dependencies": {
"svelte": "^5.0.0", // ❌ Belongs in apps/nuru-svelte
"react": "^18.0.0" // ❌ Belongs in specific app
},
"devDependencies": {
"turbo": "^2.7.1" // ✅ OK - repo tool
}
}# Install dependencies for ALL workspaces
pnpm install
# Add a dependency to a specific workspace
pnpm add <package> --filter <workspace-name>
# Examples:
pnpm add lodash --filter nuru-svelte
pnpm add -D @types/node --filter @nuru/wasm
# Add to multiple workspaces at once
pnpm add jest --save-dev --recursive --filter=nuru-svelte --filter=@nuru/wasm
# Add a workspace dependency
pnpm add @nuru/wasm --filter nuru-svelte --workspace- Turborepo does NOT manage dependencies - That's pnpm's job
- Module resolution differs between package managers - We use pnpm
- Don't reference
node_modulesdirectly in code - It's not part of the public API
# Run a task across all workspaces
pnpm dev # Run dev servers
pnpm build # Build all packages
pnpm test # Run all tests
pnpm lint # Lint all packagesTarget specific workspaces:
# Run dev only for nuru-svelte
turbo dev --filter nuru-svelte
# Build only nuru-wasm and its dependents
turbo build --filter @nuru/wasm...
# Run tests for everything except nuru-svelte
turbo test --filter '!nuru-svelte'| Filter | Meaning |
|---|---|
--filter nuru-svelte |
Only this workspace |
--filter @nuru/wasm... |
This workspace + all that depend on it |
--filter ...nuru-svelte |
This workspace + all its dependencies |
--filter '!nuru-svelte' |
Everything except this workspace |
# Run multiple tasks sequentially
turbo lint test build
# Tasks run in dependency order automatically
turbo build test # Builds first, then testsTurborepo caches task outputs based on:
- Input files - Source code, configs, etc.
- Environment variables - Specified in
turbo.json - Dependencies - From
package.json - Previous task outputs - If using
dependsOn
When you run a task:
- Turbo computes a hash of all inputs
- Checks if that hash exists in the cache
- If yes (cache hit) → Replays output instantly ⚡
- If no (cache miss) → Runs task and caches result
# First run - cache miss
$ turbo build
Tasks: 2 successful, 2 total
Cached: 0 cached, 2 total
Time: 15.3s
# Second run (no changes) - cache hit 🎉
$ turbo build
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 142ms >>> FULL TURBO- Local cache:
.turbo/cache/(gitignored) - Remote cache: Optional (Vercel, custom server)
# Ignore cache and re-run
turbo build --forceWhen referencing another workspace:
// apps/nuru-svelte/package.json
{
"dependencies": {
"@nuru/wasm": "workspace:*"
}
}The workspace:* protocol ensures you always use the local version.
The root package.json should be minimal - only tooling, no app code.
List all possible build outputs in turbo.json for different frameworks:
"outputs": [
"dist/**", // Vite
".next/**", // Next.js
".svelte-kit/**", // SvelteKit
"build/**", // Generic
"*.wasm" // WASM files
]Don't run all tasks if you're only working on one package:
# Only dev the web app
turbo dev --filter nuru-svelteAlways commit the lockfile to ensure reproducible builds.
Our config enables Turbo's Terminal UI for better task visibility:
{
"ui": "tui"
}# Start all dev servers
pnpm dev
# Start specific workspace
turbo dev --filter nuru-svelte
# Start workspace and its dependencies
turbo dev --filter ...nuru-svelte# Build everything
pnpm build
# Build specific workspace
turbo build --filter @nuru/wasm
# Build with force (ignore cache)
turbo build --force# Run all tests
pnpm test
# Test specific workspace
turbo test --filter nuru-svelte# Lint everything
pnpm lint
# Lint specific workspace
turbo lint --filter nuru-svelte# Install all dependencies
pnpm install
# Add dependency to workspace
pnpm add <package> --filter <workspace>
# Remove dependency from workspace
pnpm remove <package> --filter <workspace>
# Update all dependencies
pnpm update --recursive
# Check for outdated dependencies
pnpm outdated --recursive# List all workspaces
pnpm list --depth=0
# Show workspace dependency graph
pnpm list --recursive --depth=1
# Show why a package is installed
pnpm why <package>Symptoms:
WARNING Unable to calculate transitive closures: Workspace 'apps/nuru-svelte' not found in lockfile.
Solution:
# Reinstall dependencies
rm -rf node_modules pnpm-lock.yaml
pnpm installSymptoms:
WARNING: This project should be using pnpm, but was run with npm
Solution:
Our preinstall script enforces pnpm. Make sure you're using:
pnpm install # ✅ Correct
npm install # ❌ Will failSymptoms:
Error: Cannot find module '@nuru/wasm'
Solution:
# Rebuild symlinks
pnpm install
# Or manually link
cd packages/nuru-wasm
pnpm link
cd ../../apps/nuru-svelte
pnpm link @nuru/wasmSymptoms: Latest changes not reflected despite cache hit.
Solution:
# Clear cache
rm -rf .turbo
# Or force re-run
turbo build --forceSymptoms:
WARNING No locally installed `turbo` found. Using global version.
Solution:
# Install turbo locally
pnpm add turbo -D -w
# Update to latest
pnpm update turbo -w- 📖 Turborepo Docs
- 📖 pnpm Workspace Docs
- 📖 Plop.js Docs
- 🎓 Turborepo Configuration Reference
- 🎓 Managing Dependencies Guide
- 🔗 Our PR Discussion
If you have questions about the monorepo setup, please:
- Check this guide first
- Review the Turborepo documentation
- Ask in our team chat or open a GitHub Discussion
Happy coding! 🚀