vite-mastery

11.1 · difficulty 2/4 · 12 min read

pnpm Workspace Basics

pnpm workspace is the foundation of a Vite monorepo — the workspace protocol, dependency hoisting strategy, and the tradeoffs between Hoisting and Isolation.

Vite 8.1Stable

pnpm-workspace.yaml

Declare which directories in your monorepo are workspace packages:

yaml
packages:
  - "apps/*" # all directories under apps/
  - "packages/*" # all directories under packages/
  - "examples/*" # all directories under examples/
  - "!**/node_modules/**" # exclude node_modules

Glob syntax:

  • apps/*: all directories one level under apps/
  • packages/**: all levels under packages/
  • !pattern: exclude matching directories

The workspace:* Protocol

Use the workspace:* protocol to reference local packages from one another:

json
{
  "dependencies": {
    "@vite-mastery/ui": "workspace:*",
    "@vite-mastery/mdx-components": "workspace:*"
  }
}

What workspace:* means:

  • During development: links to the local packages/ui/ directory (symlink)
  • When publishing: replaced with the actual version number (if publishing to npm)
  • When not publishing: keep workspace:* as-is

Common pnpm Workspace Commands

bash
# Operate on all workspaces from the root
pnpm -r run build       # recursively run build for all packages
pnpm -r run typecheck   # recursively run typecheck

# Operate on a specific package only
pnpm --filter @vite-mastery/web dev
pnpm --filter ./packages/ui build

# Install a dependency in a specific package
pnpm add react --filter @vite-mastery/web
pnpm add -D typescript --filter @vite-mastery/ui

# Install dependencies for all packages
pnpm install

Key .npmrc Configuration

ini
# Do not hoist node_modules to the root (stricter isolation)
shamefully-hoist=false

# Automatically install peer dependencies
auto-install-peers=true

# Strict mode: dependencies not declared in package.json cannot be used
# (prevents phantom dependencies)
strict-peer-dependencies=false

The Tradeoffs of Dependency Hoisting

pnpm does not hoist dependencies to the root node_modules by default:

text
# Hoisting off (pnpm default):
apps/web/node_modules/react     ← web's own react
packages/ui/node_modules/react  ← ui's own react
# Each package has its own node_modules — more isolated

# Hoisting on (shamefully-hoist=true):
node_modules/react              ← one shared react for all packages
# Saves disk space, but risks phantom dependencies

Recommendation: keep shamefully-hoist=false (the pnpm default) and use auto-install-peers=true to handle peer deps.

Workspace Details That Matter For Vite

A Vite monorepo differs from a plain Node monorepo because source packages may enter the dev server directly. packages/ui/src/Button.tsx can be imported by apps/web, then transformed and hot-updated by Vite.

That creates a few rules:

  1. Local packages should declare exports clearly in package.json; do not rely only on path aliases.
  2. Framework runtimes such as React or Vue belong in peerDependencies for component packages.
  3. If an app consumes local package source, make sure Vite can process that package's TS/JSX/CSS.
  4. If a local package is built before consumption, make sure dist, types, and exports agree.

The most common failure is "React became two Reacts". If apps/web and packages/ui install separate React instances, HMR, context, and hooks can break. The fix is not to blindly enable hoisting; it is to put React in the component package's peerDependencies and let the app provide the single runtime.

How To Read pnpm --filter

pnpm --filter is the daily productivity tool in a monorepo:

bash
pnpm --filter @my/web dev
pnpm --filter @my/ui build
pnpm --filter "...@my/web" build
pnpm --filter "@my/ui..." test

Common meanings:

PatternMeaning
@my/webSelect only that package
...@my/webSelect @my/web and its dependencies
@my/ui...Select @my/ui and packages that depend on it
./packages/uiSelect by path

Once you use filters well, you do not need pnpm -r run build for every change. In a large monorepo, that directly improves feedback speed.

Self-check

  1. What is the difference between workspace:* and workspace:^1.0.0?
  2. What effect does shamefully-hoist=false have on your project? Why is it called "shamefully"?
  3. What is the difference between pnpm -r run build and Turborepo's turbo build?
  4. If packages/ui needs to use utility functions from packages/utils, how should you configure that?
yaml
# Configure pnpm-workspace.yaml for a monorepo with the following structure:
# - apps/web (Next.js main site)
# - apps/docs (documentation site)
# - packages/design-tokens (design tokens)
# - packages/ui (component library)
# - packages/hooks (React hooks)
# - packages/utils (utility functions)
# - examples/ contains multiple standalone examples that should NOT be part of the workspace

# Write the contents of pnpm-workspace.yaml and .npmrc