Proper sideEffects flag configuration is a foundational requirement for modern JavaScript library distribution. Incorrect declarations either bloat consumer bundles or strip critical runtime initialization code. This guide provides exact configuration patterns, resolution mechanics, and CI guardrails for library maintainers, platform engineers, and DevOps teams.

Module Side Effects: ESM vs CJS Resolution Mechanics

Bundlers rely on static analysis to determine whether a module can be safely pruned. In ESM, import and export statements are statically analyzable, allowing tools to construct deterministic dependency graphs. CommonJS (require()), by contrast, executes dynamically at runtime, making static guarantees impossible. When a package declares "sideEffects": false, bundlers assume that importing a module solely for its exports will not trigger global state mutations, network requests, or DOM modifications.

Understanding these resolution mechanics is critical when establishing the foundational goals of module pruning and consumer bundle reduction. For comprehensive strategies on minimizing payload weight across modern toolchains, refer to Tree-Shaking & Bundle Optimization.

Resolution Boundaries & Dual Distribution

When publishing dual-format packages, the exports field dictates which entry point the bundler resolves. Tree-shaking boundaries are strictly enforced at the module level, not the package level.

{
  "name": "@scope/library",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.cjs"
    },
    "./utils": {
      "import": "./dist/esm/utils.js",
      "require": "./dist/cjs/utils.cjs"
    }
  },
  "sideEffects": false
}

️ Hazard Prevention:** CJS fallbacks cannot be statically analyzed. If a consumer resolves to the require path, sideEffects: false is ignored by most bundlers. Always document CJS limitations and ensure ESM entry points are prioritized via moduleResolution settings ("moduleResolution": "bundler" or "node16" in tsconfig.json).

Declaring sideEffects in package.json

The sideEffects field accepts a boolean or an array of glob patterns. A blanket false maximizes dead code elimination but risks stripping essential initialization logic. Explicit arrays provide granular control.

Boolean vs Array Configuration

  • "sideEffects": false → Marks all files in the package as pure. Use only for strictly functional libraries.
  • "sideEffects": ["./src/**/*.css", "./src/polyfills.js"] → Explicitly preserves files with global mutations.

Path Resolution & Glob Syntax

Paths are resolved relative to the package root. Standard minimatch syntax applies. Avoid overly broad patterns that accidentally capture pure utility modules.

{
  "sideEffects": [
    "./dist/styles/*.css",
    "./dist/runtime/polyfills.js",
    "./dist/injectors/*.js"
  ]
}

️ Hazard Prevention:** Never use * or ** without strict directory scoping. ["*.css"] will match root-level CSS but may fail to match nested assets depending on the bundler’s resolver. Always prefix with ./dist/ or ./src/ matching your build output.

When detailing how to handle non-JavaScript imports that mutate global state or inject stylesheets, consult Configuring sideEffects for CSS and Asset Imports for framework-specific loader interactions.

Cross-Bundler Validation

Bundler Resolution Behavior Validation Flag
Webpack Honors sideEffects during optimization.sideEffects pass --stats + webpack-bundle-analyzer
Vite (Rollup) Respects sideEffects during treeshake pass --report + rollup-plugin-visualizer
esbuild Ignores sideEffects by default (relies on --tree-shaking=true) --metafile + esbuild-visualizer

Run a dry-build across all target bundlers before publishing to verify array resolution.

Integration with Barrel Files and Advanced DCE

Barrel files (index.ts) aggregate exports but frequently break tree-shaking boundaries. When a consumer imports import { A } from 'lib', the bundler must parse the entire barrel file. If sideEffects: false is declared, the bundler can prune unused re-exports, but only if the re-export chain itself contains no side-effectful initialization.

When discussing how re-export chains interact with side-effect declarations and tree-shaking boundaries, see Eliminating Barrel File Anti-Patterns for structural refactoring strategies.

Synergies with Bundler DCE Passes

Accurate sideEffects metadata enables deeper Dead Code Elimination (DCE) passes. Modern bundlers perform multiple graph traversals:

  1. Module Graph Construction: Identifies reachable modules.
  2. Purity Analysis: Uses sideEffects to flag prunable nodes.
  3. Scope Hoisting & DCE: Removes unused variables, functions, and conditional branches.

For deeper insights into how bundler DCE passes leverage accurate sideEffects metadata for deeper graph pruning, review Advanced Dead Code Elimination Techniques.

Consumer Bundle Profiling

After configuring sideEffects, validate consumer impact using a reproducible test harness:

# Build consumer app with production flags
npm run build -- --mode production

# Generate bundle diff against baseline
npx bundle-diff ./dist/baseline.json ./dist/current.json

️ Hazard Prevention:** Assuming CJS modules are automatically tree-shakable when sideEffects: false is set is a critical error. Ensure dual distribution uses true ESM entry points for tree-shakable exports, and explicitly document CJS fallback limitations for consumers.

CI Validation and Automated Auditing Workflows

Library maintainers must enforce guardrails to prevent regression in side-effect declarations during automated releases. CI pipelines should verify bundle size budgets, validate glob patterns, and snapshot tree-shaking efficacy.

Pipeline Configuration

Integrate size-limit and automated glob validation into your CI workflow:

# .github/workflows/bundle-audit.yml
name: Bundle Size & Side-Effects Audit
on:
  pull_request:
    paths:
      - 'src/**'
      - 'package.json'
      - 'rollup.config.*'

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run build
      - name: Validate sideEffects globs
        run: |
          npx is-valid-glob "./dist/styles/*.css" "./dist/runtime/polyfills.js"
      - name: Enforce size budgets
        run: npx size-limit
// package.json (size-limit config)
{
  "size-limit": [
    {
      "path": "dist/esm/index.js",
      "limit": "12 kB",
      "gzip": true,
      "import": "{ createClient }"
    }
  ]
}

Automated Snapshot Testing for Tree-Shaking

Generate a deterministic module retention snapshot to detect accidental inclusion:

# Generate module graph JSON
npx rollup -c --environment BUILD:production --json > dist/graph.json

# Compare against baseline in CI
npx jest --testMatch "**/tree-shake-snapshot.test.js"

️ Hazard Prevention:** CI pipelines failing to detect bundle bloat regressions after sideEffects changes is a common platform engineering gap. Implement automated bundle size thresholds and snapshot diffing in CI to flag unexpected retention of flagged modules. Block merges if size-limit exceeds defined budgets by >2%.

Linting & Validation Scripts

Add a pre-commit hook to validate package.json syntax and glob accuracy:

{
  "scripts": {
    "validate:sideeffects": "node scripts/validate-sideeffects.js",
    "prepublishOnly": "npm run validate:sideeffects && npm run build && npx size-limit"
  }
}

Enforce these standards across all platform engineering workflows to guarantee predictable consumer bundle sizes and eliminate runtime breakage caused by over-aggressive pruning.