Skip to content

Latest commit

 

History

History
642 lines (471 loc) · 14.5 KB

File metadata and controls

642 lines (471 loc) · 14.5 KB

🚀 Nuru WASM Monorepo Guide

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.

📚 Table of Contents


What is a Monorepo?

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.

Benefits

  • 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

Why Turborepo?

Turborepo is a high-performance build system for JavaScript/TypeScript monorepos. It provides:

🚄 Speed

  • Incremental builds - Only rebuild what changed
  • Smart caching - Never do the same work twice
  • Parallel execution - Run tasks across packages simultaneously

🎯 Developer Experience

  • Simple configuration - Single turbo.json file
  • Task pipelines - Define task dependencies clearly
  • Remote caching - Share cache across team and CI (optional)

🔧 Flexibility

  • Framework agnostic - Works with any JavaScript tooling
  • Package manager agnostic - Works with npm, yarn, pnpm, bun

Repository Structure

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 Purposes

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

Understanding pnpm Workspaces

What are Workspaces?

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.

How pnpm Handles Workspaces

In our monorepo:

  • nuru-svelte is a package (name: "nuru-svelte")
  • nuru-wasm is a package (name: "@nuru/wasm")

Key Concepts:

  1. Each workspace can have its own dependencies

    // apps/nuru-svelte/package.json
    {
      "dependencies": {
        "@nuru/wasm": "workspace:*",
        "svelte": "^5.0.0"
      }
    }
  2. 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
  3. pnpm creates symlinks between workspace packages

    • Changes to packages/nuru-wasm are immediately available in apps/nuru-svelte
  4. Shared dependencies are hoisted to the root when possible

    • Saves disk space
    • Ensures version consistency

Workspace Configuration

Our pnpm-workspace.yaml:

packages:
  - "apps/*"
  - "packages/*"

This tells pnpm to treat all directories in apps/ and packages/ as workspace packages.


Working with Turborepo

The turbo.json Configuration

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": []
    }
  }
}

Task Configuration Explained

dependsOn

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."

outputs

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.wasm or static/*.wasm
  • General Node apps might output to build/**

You list all possible build artifact directories, and Turborepo caches only what exists for each workspace.

cache

Controls whether task outputs are cached:

  • true (default) - Cache outputs for reuse
  • false - Never cache (useful for dev servers, tests)

persistent

Marks long-running tasks (like dev servers) that shouldn't block other tasks:

"dev": {
  "persistent": true,
  "cache": false
}

Managing Dependencies

Best Practices

Following Turborepo's dependency management guidelines:

1. Install dependencies where they're used

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 svelte

Why?

  • 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

2. Few dependencies in the root

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
  }
}

Installing Dependencies

# 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

Important Notes

  • Turborepo does NOT manage dependencies - That's pnpm's job
  • Module resolution differs between package managers - We use pnpm
  • Don't reference node_modules directly in code - It's not part of the public API

Running Tasks

Basic Task Execution

# 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 packages

Using Filters

Target 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 Syntax

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

Running Multiple Tasks

# Run multiple tasks sequentially
turbo lint test build

# Tasks run in dependency order automatically
turbo build test    # Builds first, then tests

Caching & Performance

How Caching Works

Turborepo caches task outputs based on:

  1. Input files - Source code, configs, etc.
  2. Environment variables - Specified in turbo.json
  3. Dependencies - From package.json
  4. Previous task outputs - If using dependsOn

Cache Hits

When you run a task:

  1. Turbo computes a hash of all inputs
  2. Checks if that hash exists in the cache
  3. If yes (cache hit) → Replays output instantly ⚡
  4. If no (cache miss) → Runs task and caches result

Example

# 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

Cache Location

  • Local cache: .turbo/cache/ (gitignored)
  • Remote cache: Optional (Vercel, custom server)

Force Cache Bypass

# Ignore cache and re-run
turbo build --force

Best Practices

1. Use Workspace Dependencies Properly

When referencing another workspace:

// apps/nuru-svelte/package.json
{
  "dependencies": {
    "@nuru/wasm": "workspace:*"
  }
}

The workspace:* protocol ensures you always use the local version.

2. Keep Root Clean

The root package.json should be minimal - only tooling, no app code.

3. Define Precise Outputs

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
]

4. Use Filters Effectively

Don't run all tasks if you're only working on one package:

# Only dev the web app
turbo dev --filter nuru-svelte

5. Commit pnpm-lock.yaml

Always commit the lockfile to ensure reproducible builds.

6. Use TUI Mode

Our config enables Turbo's Terminal UI for better task visibility:

{
  "ui": "tui"
}

Common Commands

Development

# Start all dev servers
pnpm dev

# Start specific workspace
turbo dev --filter nuru-svelte

# Start workspace and its dependencies
turbo dev --filter ...nuru-svelte

Building

# Build everything
pnpm build

# Build specific workspace
turbo build --filter @nuru/wasm

# Build with force (ignore cache)
turbo build --force

Testing

# Run all tests
pnpm test

# Test specific workspace
turbo test --filter nuru-svelte

Linting

# Lint everything
pnpm lint

# Lint specific workspace
turbo lint --filter nuru-svelte

Dependency Management

# 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

Workspace Info

# 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>

Troubleshooting

Issue: "Workspace not found in lockfile"

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 install

Issue: Package Manager Mismatch

Symptoms:

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 fail

Issue: Module Not Found

Symptoms:

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/wasm

Issue: Cache is Stale

Symptoms: Latest changes not reflected despite cache hit.

Solution:

# Clear cache
rm -rf .turbo

# Or force re-run
turbo build --force

Issue: Turbo Version Mismatch

Symptoms:

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

Additional Resources


Questions?

If you have questions about the monorepo setup, please:

  1. Check this guide first
  2. Review the Turborepo documentation
  3. Ask in our team chat or open a GitHub Discussion

Happy coding! 🚀