Rfc | Ember.js RFCs
Start Date Release Date Release Versions PR link Tracking Link Stage Teams
1/22/2024
  • ember-source: 6.5.0
released
  • cli
  • data
  • framework
  • learning
  • steering
  • typescript

Deprecate `import Ember from 'ember';

Summary

This RFC proprosing deprecating all APIs that have module-based replacements, as described in RFC #176 as well as other Ember.* apis that are no longer needed.

Motivation

The import Ember from 'ember'; set of APIs is implementn as a barrel file, and properly optimizing barrel files is a lot of work, requiring integration with build time tools.

If anyone one dependency in an app's dependency tree does import ... from 'ember', every feature of the framework is shipped to your users, without any ability for you to optimize.

By removing this set of exports, we have an opportunity to shrink some apps (as some APIs are not used), improving the load performance of ember apps -- and we removing all of these gives us a chance to have a better grasp of what we can get rid of permananently.

Many of these APIs already have alternatives, and those will be called out explicitly in the Transition Path below.

Transition Path

This list is semi-exhaustive, in that it covers every export from 'ember', but may not exhaustivily provide alternatives.

Throughout the rest of this RFC, the following key will be used:

  • 🌐 to mean "this is public API"
  • πŸ”’ to mean "this is private API"
  • 🧷 to mean "this is protected API"
  • 🫣 to mean "no declared access"

Testing utilities

APIs for wiring up a test framework (e.g. QUnit, etc)

| | API | Usage: EmberObserver | -- | | - | --- | ----- | -- | |🌐 | Ember.Test | A good few | | |🌐 | Ember.Test.Adapter | ember-cli-fastboot and @ember/test-helpers | currently available at @ember/test | |🌐 | Ember.Test.QUnitAdapter | ember-cli-fastboot | | |🌐 | Ember.setupForTesting | ember-cli-fastboot | |

If needed, these will need to be moved to a module such as @ember/test.

A way to communicate with the ember-inspector

The inspector will be hit especially hard by the removal of these APIs.

A good few already have available imports though.

| | API | import | | - | --- | ------ | |πŸ”’| Ember.meta | import { meta } from '@ember/-internals/meta'; | |🌐| Ember.VERSION | import { VERSION } from '@ember/version'; | |πŸ”’| Ember._captureRenderTree | import { captureRenderTree } from '@ember/debug'; | |πŸ”’| Ember.instrument | import { instrument } from '@ember/instrumentation'; | |πŸ”’| Ember.subscribe | import { subscribe } from '@ember/instrumentation'; | |πŸ”’| Ember.Instrumentation.* | import { * } from '@ember/instrumentation'; | |🫣| Ember.ViewUtils | import * as viewUtils from '@ember/-internals/views';[^view-utils] | |πŸ”’| Ember.ViewUtils.getChildViews | import { getChildViews } from '@ember/-internals/views'; | |🫣| Ember.ViewUtils.getElementView | import { getElementView } from '@ember/-internals/views'; | |πŸ”’| Ember.ViewUtils.getRootViews | import { getRootViews } from '@ember/-internals/views'; | |πŸ”’| Ember.ViewUtils.getViewBounds | import { getViewBounds } from '@ember/-internals/views'; | |πŸ”’| Ember.ViewUtils.getViewBoundingClientRect | import { getViewBoundingClientRect } from '@ember/-internals/views'; | |πŸ”’| Ember.ViewUtils.getViewClientRects | import { getViewClientRects } from '@ember/-internals/views'; | |πŸ”’| Ember.ViewUtils.getViewElement | import { getViewElement } from '@ember/-internals/views'; | |🫣| Ember.ViewUtils.isSimpleClick | import { isSimpleClick } from '@ember/-internals/views'; | |🫣| Ember.ViewUtils.isSerializationFirstNode | import { isSerializationFirstNode } from '@ember/-internals/glimmer'; |

[^view-utils]: Not all of these exports are used for ViewUtils.

Perhaps we can have folks add this to their apps:

import { macroCondition, isDevelopingApp, importSync } from '@embroider/macros';

if (macroCondition(isDevelopingApp())) {
  // maybe this is side-effecting and installs 
  // some functions on `globalThis` that the inspector could call
  // since the inspector can't import modules from a built app.
  importSync('@ember/inspector-support');
}

No replacements.

Applies to both the value and type exports (if applicable). All of these will not be re-exported from other @ember/* packages, but the following tables will show addon usage[^why-addon-usage] in the ecosystem and potential paths forward for library authors.

[^why-addon-usage]: Addons are notorious for doing things they shouldn't, accessing private APIs, doing crazy things so users don't have to, working around ecosystem and broader ecosystem problems etc. It's also expected that addon authors will be able to handle migrations more quickly than app devs.

| | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🫣 | Ember._getPath | None | n/a | |🫣 | Ember.isNamespace | None | n/a | |🫣 | Ember.toString | None | n/a | |πŸ”’ | Ember.Container | Many, but old or docs | n/a | |πŸ”’ | Ember.Registry | Many, but old or docs | n/a |

Internal decorator utils | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🫣 | Ember._descriptor | EmberObserver: None | n/a | |πŸ”’ | Ember._setClassicDecorator | EmberObserver: ember-concurrency | n/a |

Reactivity | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |πŸ”’ | Ember.beginPropertyChanges | ember-m3 + old addons | n/a | |πŸ”’ | Ember.endPropertyChanges | ember-m3 + old addons | n/a | |πŸ”’ | Ember.changeProperties | None | n/a |

Observable | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🌐 | Ember.hasListeners | None | n/a |

Mixins | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |πŸ”’ | Ember._ContainerProxyMixin | mostly old addons. Includes ember-decorators, ember-data-has-many-query, ember-graphql-adapter, ember-cli-fastboot (in tests / test-support) | n/a | |πŸ”’ | Ember._RegistryProxyMixin | mostly old addons. Includes ember-decorators, ember-data-has-many-query, ember-graphql-adapter, ember-cli-fastboot (in tests / test-support) | n/a | |πŸ”’ | Ember._ProxyMixin | ember-bootstrap-components, 8 years ago | n/a | |πŸ”’ | Ember.ActionHandler | 'ember-error-tracker' + old addons. Many usages include pre-modules Ember usage. | n/a | |πŸ”’ | Ember.Comparable | ember-data-model-fragments | n/a |

Utility | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🫣 | Ember.lookup | old addons, > 6 years | Use getOwner(...).lookup from @ember/owner | |🌐 | Ember.libraries | Many usages, mostly ember-data and related | This isn't a behavior that Ember needs to provide, nor should it be library authors' responsibilty to register themselves with a library listing system. App authors could choose to use any webpack or other build plugin that collections this information, such as webpack-node-modules-list or unplugin-info. | |🫣 | Ember._Cache | None | n/a | |πŸ”’ | Ember.GUID_KEY | ember-data-save-relationships, 6 years ago | n/a | | πŸ”’ | Ember.canInvoke | @summit-electric-supply | use optional chaining, e.g.: this.foo?.method?.() | |πŸ”’ | Ember.generateGuid | [ember-flexberry + old addons](https://emberobserver.com/code-search?codeQuery=Ember.generateGuid&sort=updated&sortAscending=false) | Use [guidFor](https://api.emberjs.com/ember/5.6/functions/@ember%2Fobject%2Finternals/guidFor) or [uuid](https://www.npmjs.com/package/uuid) or the browser-native [crypto.randomUUID()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) | |🌐 |Ember.uuid| [3 recent addons](https://emberobserver.com/code-search?codeQuery=Ember.uuid&sort=updated&sortAscending=false) | Use [guidFor](https://api.emberjs.com/ember/5.6/functions/@ember%2Fobject%2Finternals/guidFor) or [uuid](https://www.npmjs.com/package/uuid) or the browser-native [crypto.randomUUID()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) | |πŸ”’ |Ember.wrap| [None](https://emberobserver.com/code-search?codeQuery=Ember.wrap&sort=updated&sortAscending=false) | n/a | |πŸ”’ |Ember.inspect| [old addons](https://emberobserver.com/code-search?codeQuery=Ember.inspect&sort=updated&sortAscending=false) | n/a | |🫣 |Ember.Debug| [old addons](https://emberobserver.com/code-search?codeQuery=Ember.Debug&sort=updated&sortAscending=false) | use [@ember/debug](https://api.emberjs.com/ember/5.6/modules/@ember%2Fdebug) | |🫣 |Ember.cacheFor| [old addons](https://emberobserver.com/code-search?codeQuery=Ember.cacheFor&sort=updated&sortAscending=false) | potentially [@glimmer/tracking/primitives/cache](https://api.emberjs.com/ember/5.6/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache) | |🌐 |Ember.ComputedProperty| [aside from docs, old addons](https://emberobserver.com/code-search?codeQuery=Ember.ComputedProperty&sort=updated&sortAscending=false). Most recent usage is 3 years ago inember-cli-furnance-validation| n/a | |🫣 |Ember.RouterDSL| [old addons](https://emberobserver.com/code-search?codeQuery=Ember.RouterDSL&sort=updated&sortAscending=false) | n/a | |πŸ”’ |Ember.controllerFor| [None](https://emberobserver.com/code-search?codeQuery=Ember.controllerFor&sort=updated&sortAscending=false) | n/a | |πŸ”’ |Ember.generateController| [bitbird-core-ember-routing, 5 years ago](https://emberobserver.com/code-search?codeQuery=Ember.generateController&sort=updated&sortAscending=false) | n/a | |πŸ”’ |Ember.generateControllerFactory` | None | n/a |

| | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🌐 | Ember.VERSION | EmberObserver: Not many usages. | This has the ember version in it, but it could be converted to a virtual module to import from somewhere, such as @ember/version | |πŸ”’ | Ember._Backburner | EmberObserver: None | n/a | |🌐 | Ember.inject | EmberObserver: Many, all using classic classes. A lot of results are also classic-class docs. | Use @service |

Any projects using these are already not safe for embroider and won't work with Vite | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |🫣 | Ember.__loader | 159 addons. Some experimental. Most from @ascua | n/a | | 🫣 | Ember.__loader.require | same as Ember.__loader | n/a | | 🫣 | Ember.__loader.define | 5 addons, ~2 recent. One is ember-cli-fastboot (tests, test-support). | n/a | |🫣 | Ember.__loader.registry | 13 addons, ~5 recent. One is ember-cli-fastboot (tests, test-support). | n/a | |πŸ”’ | Ember.BOOTED | None | n/a |

Replaced by RFC #931. For scenarios where folks would like to compile templates at runtime, see RFC #931 or the code of ember-repl. | | API | Usage: EmberObserver | Migration | | - | --- | ----- | --------- | |πŸ”’ | Ember.TEMPLATES | ember-resolver | n/a | |🫣 | Ember.HTMLBars | Lots of usage (encompasses the below APIs) | n/a | |🫣 | Ember.HTMLBars.DOMHelper | ember-cli-fastboot uses protocolForURL, parseHTML | n/a | |🫣 | Ember.HTMLBars.template | ember-cli-fastboot (and ember-ast-hot-load) | n/a | |🫣 | Ember.HTMLBars.compile | old addons | n/a | |🫣 | Ember.HTMLBars.precomple | ember-ast-hot-load | n/a | |🫣 | Ember.Handlebars | 174 addons, mostly @ascua and also a lot of mentions in docs. | n/a | |🫣 | Ember.Handlebars.template | None | n/a | |🫣 | Ember.Handlebars.Utils.escapeExpression | 100 addons, mostly @ascua | Removed in ember.js PR#20360 as it is not public API. | |🫣 | Ember.Handlebars.compile | ember-cli-fastboot and ember-collection | n/a | |🫣 | Ember.Handlebars.precomple | None | n/a |

Other APIs

  • 🫣 Ember.testing
    Instead, use
  import { macroCondition, isTesting } from '@embroider/macros';

  // ...

  if (macroCondition(isTesting())) {
    // test only code here
  }
  • 🌐 Ember.onerror Instead you may be able to use an event listener for the error event on window.
  window.addEventListener('error', /* ... event handler ... */);

For promise rejections, you'll want to use the unhandledrejection event.

  window.addEventListener('unhandledrejection', /* ... event handler ... */);

If you really need the original behavior:

  import { getOnerror, setOnerror } from '@ember/-internals/error-handling';

But this should not be needed.

Imports Available

Most of this is covered in RFC #176

Unless otherwise stated, there will not be usage-based decision on these, as they all exist under available imports today.

| | Ember. API | Use this instead | | - | ------------ | ---------------- | |🌐 | Ember.FEATURES | import { isEnabled, FEATURES } from '@ember/canary-features'; | |🌐 | Ember._setComponentManager | import { setComponentManager } from '@ember/component'; | |🌐 | Ember._componentManagerCapabilities | import { capabilities } from '@ember/component'; | |🌐 | Ember._modifierManagerCapabilities | import { capabilities } from '@ember/modifier'; | |🌐 | Ember._createCache | import { createCache } from '@glimmer/tracking/primitives/cache'; RFC #615 | |🌐 | Ember._cacheGetValue | import { getValue } from '@glimmer/tracking/primitives/cache'; RFC #615 | |🌐 | Ember._cacheIsConst | import { isConst } from '@glimmer/tracking/primitives/cache'; RFC #615 | |🌐 | Ember._tracked | import { tracked } from '@glimmer/tracking'; | |🌐 | Ember.RSVP | import RSVP from 'rsvp'; | |🌐 | Ember.guidFor | import { guidFor } from '@ember/object/internals'; | |🌐 | Ember.getOwner | import { getOwner } from '@ember/owner'; | |🌐 | Ember.setOwner | import { setOwner } from '@ember/owner'; | |🌐 | Ember.onLoad | import { onLoad } from '@ember/application'; | |🌐 | Ember.runLoadHooks | import { runLoadHooks } from '@ember/application'; | |🌐 | Ember.Application | import Application from '@ember/application'; | |🌐 | Ember.ApplicationInstance | import ApplicationInstance from '@ember/application/instance'; | |🌐 | Ember.Namespace | import Namespace from '@ember/application/namespace'; | |🌐 | Ember.A | import { A } from '@ember/array'; | |🌐 | Ember.Array | import Array from '@ember/array'; | |🌐 | Ember.NativeArray | import { NativeArray } from '@ember/array'; | |🌐 | Ember.isArray | import { isArray } from '@ember/array'; | |πŸ”’ | Ember.makeArray | import { makeArray } from '@ember/array'; | |🌐 | Ember.MutableArray | import MutableArray from '@ember/array/mutable'; | |🌐 | Ember.ArrayProxy | import ArrayProxy from '@ember/array/proxy'; | |🌐 | Ember._Input | import { Input } from '@ember/component'; | |🌐 | Ember.Component | import Component from '@ember/component'; | |🌐 | Ember.Helper | import Helper from '@ember/component/helper'; | |🌐 | Ember.Controller | import Controller from '@ember/controller'; | |πŸ”’ | Ember.ControllerMixin | import { ControllerMixin } from '@ember/controller'; | |🌐 | Ember.assert | import { assert } from '@ember/debug'; | |🌐 | Ember.warn | import { warn } from '@ember/debug'; | |🌐 | Ember.debug | import { debug } from '@ember/debug'; | |🌐 | Ember.deprecate | import { deprecate } from '@ember/debug'; | |🫣 | Ember.deprecateFunc | import { deprecateFunc } from '@ember/debug'; | |🌐 | Ember.runInDebug | import { runInDebug } from '@ember/debug'; | |🌐 | Ember.Debug.registerDeprecationHandler | import { registerDeprecationHandler } from '@ember/debug'; | |🌐 | Ember.ContainerDebugAdapter | import ContainerDebugAdapter from '@ember/debug/container-debug-adapter'; | |🌐 | Ember.DataAdapter | import DataAdapter from '@ember/debug/data-adapter'; | |🌐 | Ember._assertDestroyablesDestroyed | import { assertDestroyablesDestroyed } from '@ember/destroyable'; | |🌐 | Ember._associateDestroyableChild | import { associateDestroyableChild } from '@ember/destroyable'; | |🌐 | Ember._enableDestroyableTracking | import { enableDestroyableTracking } from '@ember/destroyable'; | |🌐 | Ember._isDestroying | import { isDestroying } from '@ember/destroyable'; | |🌐 | Ember._isDestroyed | import { isDestroyed } from '@ember/destroyable'; | |🌐 | Ember._registerDestructor | import { registerDestructor } from '@ember/destroyable'; | |🌐 | Ember._unregisterDestructor | import { unregisterDestructor } from '@ember/destroyable'; | |🌐 | Ember.destroy | import { destroy } from '@ember/destroyable'; | |🌐 | Ember.Engine | import Engine from '@ember/engine'; | |🌐 | Ember.EngineInstance | import Engine from '@ember/engine/instance'; | |πŸ”’ | Ember.Enumerable | import Enumerable from '@ember/enumerable'; | |πŸ”’ | Ember.MutableEnumerable | import MutableEnumerable from '@ember/enumerable/mutable'; | |🌐 | Ember.Object | import Object from '@ember/object'; | |🌐 | Ember._action | import { action } from '@ember/object'; | |🌐 | Ember.computed | import { computed } from '@ember/object'; | |🌐 | Ember.defineProperty | import { defineProperty } from '@ember/object'; | |🌐 | Ember.get | import { get } from '@ember/object'; | |🌐 | Ember.getProperties | import { getProperties } from '@ember/object'; | |🌐 | Ember.notifyPropertyChange | import { notifyPropertyChange } from '@ember/object'; | |🌐 | Ember.observer | import { observer } from '@ember/object'; | |🌐 | Ember.set | import { set } from '@ember/object'; | |🌐 | Ember.trySet | import { trySet } from '@ember/object'; | |🌐 | Ember.setProperties | import { setProperties } from '@ember/object'; | |🌐 | Ember._dependentKeyCompat | import { dependentKeyCompat } from '@ember/object/compat'; | |🌐 | Ember.expandProperties | import { expandProperties } from '@ember/object/computed'; | |🌐 | Ember.CoreObject | import EmberObject from '@ember/object'; | |🌐 | Ember.Evented | import Evented from '@ember/object/evented'; | |🌐 | Ember.on | import { on } from '@ember/object/evented'; | |🌐 | Ember.addListener | import { addListener } from '@ember/object/events'; | |🌐 | Ember.removeListener | import { removeListener } from '@ember/object/events'; | |🌐 | Ember.sendEvent | import { sendEvent } from '@ember/object/events'; | |🌐 | Ember.Mixin | import Mixin from '@ember/object/mixin'; | |πŸ”’ | Ember.mixin | import { mixin } from '@ember/object/mixin'; | |🌐 | Ember.Observable | import Observable from '@ember/object/observable'; | |🌐 |Ember.addObserver | import { addObserver } from '@ember/object/observers'; | |🌐 | Ember.removeObserver | import { removeObserver } from '@ember/object/observers'; | |🌐 | Ember.PromiseProxyMixin | import EmberPromiseProxyMixin from '@ember/object/promise-proxy-mixin'; | |🌐 | Ember.ObjectProxy | import ObjectProxy from '@ember/object/proxy'; | |🧷 | Ember.HistoryLocation | import HistoryLocation from '@ember/routing/history-location'; | |🧷 | Ember.HashLocation | import HashLocation from '@ember/routing/hash-location'; | |🧷 | Ember.NoneLocation | import NoneLocation from '@ember/routing/none-location'; | |🌐 | Ember.Route | import Route from '@ember/routing/route'; | |🌐 | Ember.run | import { run } from '@ember/runloop'; | |🌐 | Ember.Service | import Service from '@ember/service'; | |🌐 | Ember.compare | import { compare } from '@ember/utils'; | |🌐 | Ember.isBlank | import { isBlank } from '@ember/utils'; | |🌐 | Ember.isEmpty | import { isEmpty } from '@ember/utils'; | |🌐 | Ember.isEqual | import { isEqual } from '@ember/utils'; | |🌐 | Ember.isPresent | import { isPresent } from '@ember/utils'; | |🌐 | Ember.typeOf | import { typeOf } from '@ember/utils'; | |🌐 | Ember._getComponentTemplate | import { getComponentTemplate } from '@ember/component'; | |🌐 | Ember._setComponentTemplate | import { setComponentTemplate } from '@ember/component'; | |🌐 | Ember._helperManagerCapabilities | import { capabilities } from '@ember/helper'; | |🌐 | Ember._setHelperManager | import { setHelperManager } from '@ember/helper'; | |🌐 | Ember._setModifierManager | import { setModifierManager } from '@ember/modifier'; | |🌐 | Ember._templateOnlyComponent | import templateOnly from '@ember/component/template-only'; | |🌐 | Ember._invokeHelper | import { invokeHelper } from '@ember/helper'; | |🌐 | Ember._hash | import { hash } from '@ember/helper'; | |🌐 | Ember._array | import { array } from '@ember/helper'; | |🌐 | Ember._concat | import { concat } from '@ember/helper'; | |🌐 | Ember._get | import { get } from '@ember/helper'; | |🌐 | Ember._on | import { on } from '@ember/modifier'; | |🌐 | Ember._fn | import { fn } from '@ember/helper'; | |🌐 | Ember.ENV | import MyEnv from '<my-app>/config/environment'; (for apps) or owner.resolveRegistration('config:environment') for addons|

Implementation Plan

These can happen in any order

  • Add deprecations to each Ember.* access

  • Add the Testing utilities to @ember/test, if needed.

  • Add an @ember/version package to ember-source

  • Add re-exports of private APIs, ComputedProperty, and _setClassicDecorator These will still be deprecated on Ember., and will be deprecated themselves as we progress through deprecating Ember Classic.

  • Update ember-inspector to use imports for the internals and instrumentation APIs

  • Add @ember/inspector-support to ember-source to manage things like LIBRARIES.

    import { libraries } from '@ember/inspector-support';
    
    libraries.add('ember-data', '5.3.1');
    // and/or
    libraries.addAll(depInfoFromPlugin);
  • Add deprecation guide entries for each API

How We Teach This

While @ember/-internals were created to be internal, introducing new names for them would create churn and would make it harder for addon authors to support a wide range of versions. The internals paths all work today on supported releases, so dropping the deprecated usage doesn't reduce your support matrix, whereas using a newly-introduced import path would.

All @ember/-internals(/*)? APIs mentioned above are now public API, and to remove any of those APIs, they will need to go through the deprecation process.


The guides already use the modern imports where available.

There is a place that needs updating, around advanced debugging, where folks configure Backburner to be in debug mode.

  • https://guides.emberjs.com/release/applications/run-loop/#toc_where-can-i-find-more-information
  • https://guides.emberjs.com/release/configuring-ember/debugging/#toc_errors-within-emberrunlater-backburner
  • Access to backburner here isn't relevant though because it's accessed from the run import from @ember/runloop

When using embroider and staticEmberSource: true, the benefits of not having this file can be realized in apps (as long as the app and all consumed addons do not import from 'ember')

Available Codemods

  • https://github.com/ember-codemods/ember-modules-codemod (from the work of RFC 176)

Deprecation Guide

  • Separate ids for each API so that folks don't have to scroll too far to get to their migration path (if a migration path exists).
  • Mostly using the above tables, but without the Usage: EmberObserver column.

Drawbacks

n/a, to be more module-friendly, we must get rid of the 'ember' import.

Alternatives

Don't use @ember/-internals and create new public APIs for all of the things currently under @ember/-internals. This would create a lot of churn in the ecosystem, when we want to get rid of some of these APIs anyway.

Unresolved questions

n/a

Q: Do our instrumentation and internals sub-packages have any SemVer guarantees? Or are we allowed to "do what we need to" and not care about public-facing SemVer? A: If something is privately but heavily used, we will try to deprecate before removing the API and make sure the deprecation makes it in to an LTS before that removal.