diff --git a/text/1247-build-time-feature-flags.md b/text/1247-build-time-feature-flags.md new file mode 100644 index 0000000000..90ed0ce98b --- /dev/null +++ b/text/1247-build-time-feature-flags.md @@ -0,0 +1,152 @@ +--- +stage: accepted +start-date: 2026-09-30T00:00:00.000Z +release-date: +release-versions: +teams: + - cli + - framework + - learning +prs: + accepted: https://github.com/emberjs/rfcs/pull/1247 +project-link: +suite: +--- + + +# Build-time Feature Flags + +## Summary + +`ember-source` reads each flag with the literal expression `import.meta.env?.EMBER_*`: + +```js +if (import.meta.env?.EMBER_SYNC_OBSERVERS) { + // sync observer path +} +``` + +The bundler replaces the expression with a literal, and the minifier removes the branch. +Each flag defaults to falsy, so an app that sets nothing gets the default behavior. + +`EmberENV`, `config/environment.js`, and optional features work the same until v8. +`EMBER_DROP_LEGACY_CONFIG_ENV` removes that code now. +Minimal apps with no config files get a way to set flags for the first time. + +A separate RFC will be needed for deprecations of the old styles of flagging. + +## Motivation + +Today, all of Ember's flags are read at runtime from `window.EmberENV`, so every branch ships to every user: +- an app that turned on `default-async-observers` years ago still ships the sync observer path +- an app that turns off `EmberObject` ([RFC 1234](./1234-deprecate-ember-object.md)) would still ship `EmberObject` + +While `ember-source` reads `EmberENV` at runtime, the build cannot remove either branch. +`EMBER_DROP_LEGACY_CONFIG_ENV` removes that read, so `import.meta.env` becomes the one source of truth. + +Minimal apps, such as [`v2-app-hello-world-template`](https://github.com/emberjs/ember.js/tree/main/smoke-tests/v2-app-hello-world-template) and the [`ember.nvp`](https://github.com/NullVoxPopuli/ember.nvp) `minimal-app`, have no `config/environment.js` and no `@embroider/virtual/vendor.js`, so they have no way to set a flag at all. + +## Detailed design + +- Each read is the literal expression `import.meta.env?.EMBER_SOME_FEATURE`. No module re-exports a flag or gives it a second name, so a build tool only needs expression replacement to remove dead code. +- Each flag defaults to falsy. A flag turns on a non-default behavior. +- When a major makes an optional behavior the default, its flag goes away. If the old behavior stays available, it gets a new flag. +- The `?.` is for environments without `import.meta.env` (plain `