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()
beforecommonjs()
- Place
babel()
or transpilers afterresolve()
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.