Ember CLI and Broccoli: Production Bundle Resolution Discrepancies
0 reputation · 19 Jan 2022, 20:41 UTC
0 reputation · 19 Jan 2022, 20:41 UTC
In Ember.js applications, there is a functional divergence between the development server and the production build process. While ember serve maintains ES modules to facilitate hot-module replacement, ember build --environment=production utilizes the Broccoli pipeline to transpile assets into minified CommonJS or AMD formats.
The config/environment.js file provides different values based on process.env.EMBER_ENV. Logic that branches on this variable may result in the exclusion of specific modules or polyfills during the production optimization phase, which are otherwise present during local development.
When an addon or application utilizes default exports that are not strictly compatible with the production transpilation target, the resulting bundle may fail to resolve symbols that were accessible in the unminified development environment.
process.env.EMBER_ENV branching on the tree-shaking process for side-effect-only modules?33775 reputation · 19 Jan 2022, 22:25 UTC
The Broccoli pipeline does not inherently handle default export resolution differently between development and production; rather, the divergence occurs during the transpilation and minification phases that follow the Broccoli file-tree construction. In development, assets are typically served unminified, preserving the original structure of Babel's interop helpers. In production, the minifier (such as Terser) optimizes these helpers and mangles property names, which can break modules that rely on non-strict default export resolution or runtime property names.
Branching on process.env.EMBER_ENV within config/environment.js creates static constants that are baked into the application at build time. The impact on tree-shaking is as follows:
EMBER_ENV value (e.g., if (ENV.EMBER_ENV === 'development') { ... }), the minifier can remove the unreachable code path.import './polyfills';), it is generally preserved in the production bundle regardless of EMBER_ENV, unless the entire import statement is wrapped in a dead-code block.To resolve symbol resolution failures that only appear in production, apply these targeted checks:
export default with named exports in shared addons to avoid reliance on Babel's _interopRequireDefault helper, which is a common point of failure during minification.config/environment.js for logic that conditionally excludes polyfills or modules based on the environment. Ensure that any dependency required for production functionality is not gated behind a development-only check.app.js or a similar entry point, rather than inside a conditional block that the minifier may be stripping.To provide a more precise recommendation, please specify if the project is using classic Ember CLI or the Embroider build system, as the latter replaces much of the Broccoli-based bundling with Webpack/Rollup, which handles tree-shaking and module resolution fundamentally differently.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 19 Jan 2022, 21:44 UTC
During ember serve, the Broccoli pipeline runs ember-cli-babel with the es2015 preset, so ES module syntax (including export default) stays unchanged and can be consumed directly by the browser’s native loader or by HMR.
In a production build (ember build --environment=production), the same Babel step adds the amd or commonjs output preset. This transforms an ES default export into a module object that has a default property (e.g., var foo = function foo() {}; module.exports = { default: foo };). Consequently, code that does import foo from 'addon' receives an object whose foo property is undefined unless the bundler provides interop handling or the import is written as import { default as foo } from 'addon'.
The process.env.EMBER_ENV flag in config/environment.js can cause Broccoli to skip files marked sideEffectOnly: true when the value is production, but this only affects tree‑shaking if the Broccoli funnel is configured with an exclude step; otherwise the module remains in the bundle regardless of the environment flag.