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 in
ember-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 theerror
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.*
accessAdd the Testing utilities to
@ember/test
, if needed.Add an
@ember/version
package toember-source
Add re-exports of private APIs,
ComputedProperty
, and_setClassicDecorator
These will still be deprecated onEmber.
, 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
toember-source
to manage things likeLIBRARIES
.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.