DEV Community

Wilson Xu
Wilson Xu

Posted on

10 npm Scripts Patterns That Replace Your Build Tools

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/"
  }
}
Enter fullscreen mode Exit fullscreen mode

When you run npm run build, npm executes: prebuildbuildpostbuild. 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:*"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

For cross-platform compatibility, use cross-env:

{
  "scripts": {
    "start:dev": "cross-env NODE_ENV=development nodemon src/server.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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'"
  }
}
Enter fullscreen mode Exit fullscreen mode

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)