npm workspaces 멀티 패키지 관리
Managing monorepo projects with npm workspaces including shared dependencies, cross-package scripts, and publishing.
npm workspaces 멀티 패키지 관리
Introduction
As projects grow, monorepo structures with multiple packages become necessary. npm workspaces provide a built-in way to manage multiple packages in a single repository with shared dependencies and scripts. This post covers setting up and using npm workspaces effectively.
Environment
node --version
# v20.11.0
npm --version
# 10.2.4Problem
Managing multiple packages with separate repositories leads to:
- Dependency duplication across packages
- Version conflicts between packages
- Complex cross-package development
- Difficult publishing workflows
project/
├── package-a/
│ ├── node_modules/ ← Duplicate dependencies
│ └── package.json
├── package-b/
│ ├── node_modules/ ← More duplicates
│ └── package.json
└── package.json ← No workspace managementAnalysis
npm workspaces solve these problems by:
- Hoisting shared dependencies to the root
node_modules - Creating symlinks for local packages
- Running scripts across multiple packages
- Managing versions consistently
The workspace structure:
project/
├── packages/
│ ├── package-a/
│ │ └── package.json
│ └── package-b/
│ └── package.json
├── node_modules/ ← Shared dependencies
├── package.json ← Workspace root
└── package-lock.json ← Single lockfileSolution
Step 1: Create workspace structure
// root package.json
{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces",
"lint": "npm run lint --workspaces",
"clean": "npm run clean --workspaces"
}
}Step 2: Create workspace packages
// packages/package-a/package.json
{
"name": "@myorg/package-a",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"build": "tsc",
"test": "jest"
},
"dependencies": {
"lodash": "^4.17.21"
}
}// packages/package-b/package.json
{
"name": "@myorg/package-b",
"version": "1.0.0",
"main": "src/index.js",
"dependencies": {
"@myorg/package-a": "^1.0.0",
"axios": "^1.6.0"
}
}Step 3: Install dependencies
# Install all dependencies
npm install
# Add dependency to specific workspace
npm install lodash --workspace=@myorg/package-a
# Add dev dependency to root
npm install jest --save-dev
# List workspaces
npm workspaces list
# packages/package-a
# packages/package-bStep 4: Run scripts across workspaces
# Run build in all workspaces
npm run build --workspaces
# Run build in specific workspace
npm run build --workspace=@myorg/package-a
# Run test in all workspaces
npm run test --workspaces
# Run command in all workspaces
npm exec --workspaces -- commandStep 5: Cross-package dependencies
// packages/package-b/package.json
{
"name": "@myorg/package-b",
"dependencies": {
"@myorg/package-a": "*"
}
}// packages/package-b/src/index.js
const packageA = require('@myorg/package-a');
console.log(packageA.doSomething());Step 6: Workspace-specific configuration
// packages/package-a/package.json
{
"name": "@myorg/package-a",
"scripts": {
"build": "tsc",
"test": "jest",
"lint": "eslint src/",
"prepublishOnly": "npm run build"
},
"files": [
"dist",
"src"
],
"publishConfig": {
"access": "public"
}
}Step 7: Publishing workspaces
# Publish all workspaces
npm publish --workspaces
# Publish specific workspace
npm publish --workspace=@myorg/package-a
# Dry run
npm publish --dry-run --workspacesStep 8: Advanced workspace scripts
// root package.json
{
"scripts": {
"dev": "npm run dev --workspaces --if-present",
"build:all": "npm run build --workspaces --if-present",
"test:all": "npm run test --workspaces --if-present",
"version:patch": "npm version patch --workspaces --no-git-tag-version",
"clean:all": "rm -rf node_modules packages/*/node_modules",
"lint:fix": "npm run lint:fix --workspaces --if-present"
}
}Lessons Learned
- Use scoped packages -
@myorg/package-nameprevents naming conflicts - Keep
private: truein root - The root package should not be published - Use
--if-present- Prevents errors when scripts do not exist in all workspaces - Lock versions with package-lock.json - A single lockfile for the entire monorepo
- Consider Turborepo or Nx for complex build pipelines
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.