10 npm Scripts Patterns That Replace Your Build Tools
Most JavaScript projects reach for Webpack, Gulp, or custom build scripts when npm scripts alone could handle the job. The scripts field in package.json is more powerful than most developers realize — it can handle build pipelines, watch modes, pre/post hooks, cross-platform commands, and parallel task execution.
Here are 10 patterns that eliminate the need for separate build tools in many projects.
1. Pre/Post Hooks
npm automatically runs pre and post scripts around any named script:
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"postbuild": "cp package.json dist/"
}
}
When you run npm run build, npm executes: prebuild → build → postbuild. No task runner needed.
This works for any script name. pretest runs before test. postdeploy runs after deploy. It's a built-in pipeline system.
2. Parallel Execution with npm-run-all
{
"scripts": {
"build": "npm-run-all --parallel build:*",
"build:js": "esbuild src/index.ts --bundle --outfile=dist/index.js",
"build:css": "postcss src/styles.css -o dist/styles.css",
"build:types": "tsc --emitDeclarationOnly"
}
}
The build:* glob pattern runs all scripts starting with build: in parallel. Three builds running simultaneously, zero config files.
3. Watch Mode with nodemon
{
"scripts": {
"dev": "nodemon --exec 'npm run build' --watch src --ext ts,css",
"dev:server": "nodemon src/server.ts",
"dev:all": "npm-run-all --parallel dev:*"
}
}
4. Environment-Specific Scripts
{
"scripts": {
"start": "node dist/server.js",
"start:dev": "NODE_ENV=development nodemon src/server.ts",
"start:prod": "NODE_ENV=production node dist/server.js",
"start:test": "NODE_ENV=test node dist/server.js"
}
}
For cross-platform compatibility, use cross-env:
{
"scripts": {
"start:dev": "cross-env NODE_ENV=development nodemon src/server.ts"
}
}
5. Conditional Scripts with && and ||
{
"scripts": {
"deploy": "npm run test && npm run build && npm run upload",
"safe-deploy": "npm run lint && npm run test && npm run build || echo 'Deploy aborted'",
"ci": "npm run lint && npm run test -- --coverage && npm run build"
}
}
The && operator ensures each step only runs if the previous one succeeded — a simple deploy pipeline in one line.
6. Pass Arguments with --
{
"scripts": {
"test": "vitest",
"test:watch": "npm run test -- --watch",
"test:coverage": "npm run test -- --coverage",
"test:ui": "npm run test -- --ui"
}
}
Everything after -- gets appended to the underlying command. So npm run test -- --watch runs vitest --watch.
7. The prepare Hook for Git Hooks
{
"scripts": {
"prepare": "husky install"
}
}
prepare runs automatically after npm install. Combined with Husky, this sets up git hooks for every developer who clones your repo — no manual setup required.
8. Database and Service Management
{
"scripts": {
"db:start": "docker compose up -d postgres",
"db:stop": "docker compose down",
"db:migrate": "prisma migrate deploy",
"db:seed": "tsx scripts/seed.ts",
"db:reset": "npm run db:stop && npm run db:start && sleep 2 && npm run db:migrate && npm run db:seed",
"services": "docker compose up -d"
}
}
9. Version and Release Scripts
{
"scripts": {
"release:patch": "npm version patch && git push && git push --tags && npm publish",
"release:minor": "npm version minor && git push && git push --tags && npm publish",
"release:major": "npm version major && git push && git push --tags && npm publish",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
}
}
One command to bump version, push to git, and publish to npm.
10. Validation Pipeline
{
"scripts": {
"validate": "npm-run-all --parallel lint typecheck test:unit --sequential test:e2e",
"lint": "eslint src/",
"typecheck": "tsc --noEmit",
"test:unit": "vitest run",
"test:e2e": "playwright test",
"precommit": "npm run validate",
"prepush": "npm run validate && npm run build"
}
}
Lint, typecheck, and unit tests run in parallel (fast). E2E tests run after (sequential). The entire validation suite in one command.
Bonus: Script Documentation
Add a help script that documents what's available:
{
"scripts": {
"help": "echo 'Available scripts:' && echo ' dev - Start development server' && echo ' build - Build for production' && echo ' test - Run tests' && echo ' deploy - Deploy to production'"
}
}
Or use npm run (no arguments) which lists all available scripts.
When npm Scripts Aren't Enough
These patterns handle 80% of build tool use cases. You should still reach for dedicated tools when you need:
- Complex file transformations — Webpack's loader system, Vite's plugin API
- Incremental builds — Nx and Turborepo's caching
- Build graph optimization — when build order matters across many packages
- Asset optimization — image compression, SVG spriting, etc.
But for straightforward projects? npm scripts are all you need.
Wilson Xu publishes developer tools on npm and writes about JavaScript tooling at dev.to/chengyixu.
Top comments (0)