Understanding the Problem: Inconsistent Output and Module Failures

Key Symptoms

  • Modules included in dev but missing in production builds
  • Tree-shaken dependencies incorrectly removed
  • ESM/CommonJS interop issues across plugins
  • CI builds differ from local output

Underlying Causes

  • Improper plugin ordering (e.g., resolving before transforming)
  • Misconfigured external dependencies
  • Platform-specific symlink resolution (macOS vs Linux CI)
  • Dynamic imports mishandled by certain plugins

Deep Dive: Rollup Architecture and Pitfalls

1. Plugin Order Sensitivity

Rollup executes plugins in order—misplacing resolve or transform plugins leads to unexpected behavior. A common pitfall is using rollup-plugin-node-resolve after commonjs.

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';

export default {
  input: 'src/index.js',
  plugins: [
    resolve(), // should come BEFORE commonjs
    commonjs()
  ]
};

2. External Dependency Misclassification

Marking libraries as external too aggressively can prevent Rollup from bundling them, resulting in undefined is not a function at runtime.

export default {
  external: [ 'react', 'lodash' ], // ensure only actual externals are listed
};

3. Improper Handling of ESM/CJS Interop

Rollup assumes ESM by default. Without correct interop flags, it may fail to interpret CommonJS exports correctly.

commonjs({
  include: /node_modules/,
  requireReturnsDefault: 'auto'
})

4. Ambiguous Output Formats

Incorrectly specifying output.format (e.g., using iife in a module environment) can lead to syntax errors or unusable builds.

output: {
  file: 'dist/bundle.js',
  format: 'esm', // or 'cjs', 'iife' based on runtime
}

Step-by-Step Diagnostic Flow

Step 1: Compare CI vs Local Builds

Use checksum diffing tools (e.g., shasum) to verify file integrity. Investigate environment differences (Node.js version, OS-specific path handling).

Step 2: Enable Verbose Logging

Run Rollup with the --debug or --logLevel flag to trace plugin behavior and file resolutions:

rollup -c --logLevel debug

Step 3: Analyze Output Bundle

Inspect the bundle with tools like rollup-plugin-visualizer to identify unexpectedly missing or extra modules.

Step 4: Validate Plugin Resolution

Ensure plugins are resolving the same dependency versions across environments. Lock dependencies via package-lock.json or yarn.lock.

Step 5: Test with Minimal Config

Strip the config down to input/output and incrementally add plugins to isolate failure points.

Fix Strategies and Configuration Best Practices

1. Standardize Plugin Order

  • Always use resolve() before commonjs()
  • Place babel() or transpilers after resolve()

2. Lock Node and Plugin Versions

Use .nvmrc or volta to pin Node versions. Declare strict plugin versions to prevent regression during CI runs.

3. Use preserveModules for Debugging

This option helps track how Rollup processes each module during output, useful in debugging tree-shaking issues.

output: {
  dir: 'dist/',
  format: 'esm',
  preserveModules: true
}

4. Define Globals for IIFE Builds

When using UMD/IIFE, specify globals to prevent undefined references in the browser.

output: {
  format: 'iife',
  name: 'MyLib',
  globals: { react: 'React' }
}

Long-Term Best Practices

  • Run rollup --configTest to validate your config files
  • Split bundles logically using multiple inputs and output chunks
  • Integrate Rollup analysis tools in CI for regression tracking
  • Document plugin order and rationale in version control
  • Prefer ESM libraries to avoid interop complexity

Conclusion

Rollup offers exceptional control and performance, but with that flexibility comes a responsibility to understand its execution model. In large or enterprise-grade applications, inconsistent bundles often stem from misordered plugins, unintentional externals, or environment mismatch. By adopting disciplined configuration practices, standardizing build environments, and analyzing build artifacts, engineering teams can avoid regressions and ensure reliable, portable bundles across every release channel.

FAQs

1. Why does my module work locally but fail in CI with Rollup?

CI environments may use different Node.js versions, plugin versions, or lack symlink resolution. Always pin dependencies and set environment parity.

2. What is the correct plugin order in Rollup?

Generally, use resolve() → commonjs() → babel() → terser(). Order matters due to plugin execution during different hook phases.

3. How can I debug missing modules in the final bundle?

Use the rollup-plugin-visualizer and enable preserveModules to trace how modules are included or excluded during the build.

4. Is Rollup better than Webpack for libraries?

Yes, Rollup is optimized for library bundling due to ESM-first design and better tree-shaking. For apps with dynamic loading, Webpack may be preferable.

5. How do I handle CommonJS modules that Rollup fails to interpret?

Use the commonjs plugin with requireReturnsDefault set to 'auto', and ensure the modules are included in the include regex pattern.