Summary
Make Ember feel less overwhelming to new users, and make Ember applications
start faster, by replacing the Ember
global with a first-class system for
importing just the parts of the framework you need.
Motivation
ECMAScript 2015 (also known as ES2015 or ES6) introduced a syntax for importing and exporting values from modules. Ember aggressively adopted modules, and if you've used Ember before, you're probably familiar with this syntax:
import Ember from "ember";
import Analytics from "../mixins/analytics";
export default Ember.Component.extend(Analytics, {
// ...
});
One thing to notice is that the entire Ember framework is imported as a single
package. Rather than importing Component
directly, for example, you import
Ember
and subclass Ember.Component
. (And this example still works even if
you forget the import, because we also create a global variable called
Ember
.)
Using Ember via a monolithic package or global object is not ideal for several reasons:
- It's overwhelming for learners. There's a giant list of classes and functions with no hints about how they're related. The API documentation reflects this.
- Experienced developers who don't want all of Ember's features feel like they're adding unnecessary and inescapable bloat to their application.
- The
Ember
object must be built at boot time, requiring that we ship the entire framework to the browser. This has two major costs:
- Increased download time, particularly noticeable on slower connections.
- Increased parsing/evaluation cost, which still must be paid even when assets are cached. On some browsers/devices, this can far exceed the cost of the download itself.
Defining a public API for importing parts of Ember via JavaScript modules helps us lay the groundwork for solving all of these problems.
Reducing Load Time
Modules help us eliminate unneeded code. The module syntax is statically analyzable, meaning that a tool like Ember CLI can analyze an application's source code and reliably determine which files, in both the framework and the application, are actually needed. Anything that's not needed is omitted from the final build.
This allows us to provide the file size benefits of a "small modules" approach to building web applications while retaining the productivity benefits of a complete, opinionated framework.
For example, if your application never used the Ember.computed.union
computed
property helper, Ember could detect this and remove its code when you build your
application. This technique for slimming down the payload automatically is often
referred to as tree shaking or dead code elimination.
Building the module graph doesn't just mean we get a list of files used by the application— we also know which files are used route-by-route.
We can use this knowledge to optimize boot time even more, by prioritizing sending only the JavaScript needed for the requested route, rather than the entire application.
For example, if the user requests the URL
https://app.example.com/articles/123
, the server could first send the code for
ArticlesRoute
, the Article
model, the articles
template, and any
components and framework code used in the route. Only after the route is
rendered would we start to send the remainder of the application and framework
code in the background.
Guiding Learners
We can group framework classes and utilities by functionality, making it clear what things are related and how they should work together. People can feel confident they are getting only what they need at that moment, not an entire framework that they're not sure they're benefiting from.
Modernizing Ember
Lastly, developers are growing increasingly accustomed to using JavaScript modules to import libaries. If we don't adapt to modules, Ember will feel clunky and antiquated compared to modern alternatives.
Prior Art
Initial efforts to define a module API for Ember began with the
ember-cli-shims
addon. This addon provides a set of "shim" modules
that re-export a value off the global Ember
. While this setup doesn't offer
the benefits of true modules, it did allow us to rapidly experiment with a
module API without making changes to Ember core.
Common feedback from shim users was that, while they were a net improvement, they introduced too much verbosity and were hard for beginners to remember.
An oft-cited example of this verbosity is that implementing an object and using
Ember.get
and Ember.set
requires three different imports:
import EmberObject from "ember-object";
import get from "ember-metal/get";
import set from "ember-metal/set";
In fact, one of the principles outlined in this RFC is designed to correct this verbosity; namely, that utility functions and the class they are related to should share a module.
For those who have already adopted modules via the ember-cli-shims
package, we
will provide a migration tool to rewrite shim modules into the final module API.
The static nature of the import syntax makes this even easier and more reliable
than migrating globals-based apps. The upgrade process should take no more than
a few minutes (see Migration).
This RFC also builds significantly on @zeppelin's previous ES6 modules RFC, which drove initial discussion, including the idea to use scoped packages.
Detailed Design
Terminology
- Package - a bundle of JavaScript addressable by npm and other package
managers, it may contain many modules (but has a default module, usually
called
index.js
). - Scoped Package - a namespaced package whose name starts with an
@
, likeimport Thing from "@scope/thing"
. - Module - a JavaScript file with at least one default export or named export.
- Top-Level Module - the module provided by importing a package directly,
like
import Component from "@ember/component"
. - Nested Module - a module provided at a path inside a package, like
import { addObserver } from "@ember/object/observers"
.
Module Naming & Organization
Because our goal is to eliminate the Ember
global object, any public classes,
functions or properties that currently exist on the global need an equivalent
module that can be imported.
Given how fundamental modules are to the development process, how we organize and name them impacts new learners and seasoned veterans alike. Thus we must try to find a balance between predictability for new and intermediate users, and terseness for experienced developers with large apps.
There is another goal at play: we would like to help dispel the misconception that Ember is a monolithic framework. Ideally, module names help us tell a story about Ember's layered features. Rather than inheriting the entire framework at once, you can pull in just the pieces you need.
For that reason, package names should assist the developer in understanding what capabilities are added by bringing in that new package. We should pick meaningful names, not let our public API be a by-product of how Ember's internals are organized.
A full table of proposed mappings from global to module is available in Addendum 1 - Table of Module Names and Exports by Global and Addendum 2 - Table of Module Names and Exports by Package. Because there is some implicit functionality that you get when loading Ember that is not encapsulated in a global property (for example, automatically adding prototype extensions), there is also Addendum 3 - Table of Modules with Side Effects.
Before diving in to these tables, however, it may be helpful to understand some of the thinking that guided this proposal. And keep in mind, this RFC specifies a baseline module API. Nothing here precludes adding additional models in the future, as we discover missing pieces.
Use Scoped Packages
Last year, npm introduced support for scoped packages. Scopes are similar to organizations on GitHub. They allow us to use any package name, even if it's already in use on npm, by namespacing it inside a scope.
For example, the component
package is already reserved by an
unmaintained tool; we couldn't use component
as a package name even if we
wanted to.
However, scopes allow us to create a package named component
that lives under
the @ember
scope: import Component from "@ember/component"
.
The advantages of using scoped packages, as this proposal does, are two-fold:
- "Official" packages are clearly differentiated from community packages.
- There is no risk of naming conflicts with existing community packages.
Note that actually publishing packages to npm may not be immediately necessary to implement this RFC. We should still design around this constraint so that we have the option available to us in the future. For more discussion, see the Distribution unresolved question.
Prefer Common Terminology
Module names should use terms people are more likely to be familiar with. For
example, instead of the ambiguous platform
, polyfills should be in a module
called polyfill
.
Similarly, the vast majority of advanced Ember developers couldn't crisply
articulate the difference between ember-metal
and ember-runtime
. Instead, we
should prefer ember-object
, to match how people actually talk about these
features: the Ember object model.
Organize by Mental Model
One of the biggest barriers to learning is the fact that short-term memory is limited. To understand a complex system like a modern web application, the learner must hold in their head many different concepts—more concepts than most people can reason about at once.
Chunking is a strategy for dealing with this. It means that you present concepts that are conceptually related together. When the learner needs to reason about the overall system, in their mind they can replace a group of related concepts with a single, overarching concept.
For example, if you tell someone that in order to build an Ember app, they will need to understand computed properties, actions (bubbling and closure), components, containers, registries, routes, helpers (stateful and stateless), dependent keys, controllers, route maps, observers, transitions, mixins, computed property macros, injected properties, the run loop, and array proxies—they will rightfully feel like Ember is an overwhelming, overcomplicated framework. Most people (your RFC author included) simply cannot keep this many discrete concepts in their head at once.
The day-to-day reality of building an Ember app, of course, is not nearly so complex. For those developers who stick through the learning curve, they end up with a greatly simplified mental model.
This proposal attempts to re-align module naming with that simplified mental model, placing everything into packages based on the chunk of functionality they provide:
@ember/application
- Application-level concerns, like bootstrapping, initializers, and dependency injection.@ember/component
- Classes and utilities related to UI components.@ember/routing
- Classes used for multi-page routing.@ember/service
- Classes and utilities for cross-cutting services.@ember/controller
- Classes and utilities related to controllers.@ember/object
- Classes and utilities related to Ember's object model, includingEmber.Object
, computed properties and observers.@ember/runloop
- Methods for scheduling behavior on to the run loop.
It includes a few other packages that, over time, your author hopes become either unneeded or can be moved outside of core into standalone packages:
@ember/array
- Array utilities and observation. Ideally these can be replaced with a combination of ES2015+ features and array diffing in Glimmer.@ember/enumerable
- Replaced by iterables in ES2015.@ember/string
- String formatting utilities (dasherize, camelize, etc.).@ember/map
- Replaced byMap
andWeakMap
in ES2015.@ember/polyfills
- Polyfills forObject.keys
,Object.assign
andObject.create
.@ember/utils
- Grab bag of utilities that could likely be replaced with something like lodash.
And finally, some packages that may be used by internals, extensions, or addons but are not used day-to-day by app developers:
@ember/instrumentation
- Instrumentation hooks for measuring performance.@ember/debug
- Utility functions for debugging, and hooks used by debugger tools like Ember Inspector.
Classes are Default Exports
Classes that the user is supposed to import and subclass are always the default export, never a named export. In the case where a package has more than one primary class, those classes live in a nested module.
This rule ensures there is no ambiguity about whether something is a named
export or a default export: classes are always default exports. In tandem with
the following rule (Utility Functions are Named
Exports), this also means that classes
and the functions that act on them are grouped into the same import
line.
Examples
Primary class only:
import EmberObject from "@ember/object";
Primary class plus secondary classes:
import Component from "@ember/component";
import Checkbox from "@ember/component/checkbox";
import Map from "@ember/map";
import MapWithDefault from "@ember/map/with-default";
Multiple primary classes:
import Router from "@ember/routing/router";
import Route from "@ember/routing/route";
Utility Functions are Named Exports
Functions that are only useful with a particular class, or are used most frequently with that class, are named exports from the package that exports the class.
Examples
import Service, { inject } from "@ember/service";
import EmberObject, { get, set } from "@ember/object";
In cases where there are many utility functions associated with a class, they can be further subdivided into nested packages but remain named exports:
import EmberObject, { get, set } from "@ember/object";
import { addObserver } from "@ember/object/observers";
import { addListener } from "@ember/object/events";
In the future, decorators would be included under this rule as well. In fact, designing with an eye towards decorators was a large driver behind this principle. For more discussion, see the Everything is a Named Export alternative.
One Level Deep
To avoid deep directory hierarchies with mostly-empty directories, this proposal limits nesting inside a top-level package to a single level. Deep nesting like this can add additional time to navigating the hierarchy without adding much benefit.
Java packages often have this problem due to their URL-based namespacing; see
e.g. this Java
library
where you end up with deeply nested directories, like
xLog/library/src/test/java/com/elvishew/xlog/printer/AndroidPrinterTest.java
.
This rule leads to including the type in the name of the module in some cases
where it might otherwise be grouped instead. For example, instead of
@ember/routing/locations/none
, we prefer @ember/routing/none-location
to
avoid the second level of nesting.
No Non-Module Namespaces
The global version of Ember includes several functions that also act as a namespace to group related functionality.
For example, Ember.run
can be used to run some code inside a run loop, while
Ember.run.scheduleOnce
is used to schedule a function onto the run loop once.
Similarly, Ember.computed
can be used to indicate a method should be treated as
a computed property, but computed property macros also live on Ember.computed
, like
Ember.computed.alias
.
When consumed via modules, these functions no longer act as a namespace. That's because tacking these secondary functions on to the main function requires us to eagerly evaluate them (not to mention the potential deoptimizations in JavaScript VMs by adding properties to a function object).
In practice, that means that this won't work:
// Won't work!
import { run } from "@ember/runloop";
run.scheduleOnce(function() {
// ...
});
Instead, you'd have to do this:
import { scheduleOnce } from "@ember/runloop";
scheduleOnce(function() {
// ...
});
The migration tool, described below, is designed to detect these cases and migrate them correctly.
Prototype Extensions and Other Code with Side Effects
Some parts of Ember change global objects rather than exporting classes or
functions. For example, Ember (by default) installs additional methods on
String.prototype
, like the camelize()
method.
Any code that has side effects lives in a module without any exports; importing the module is enough to produce the desired side effects. For example, if I wanted to make the string extensions available to the application, I could write:
import "@ember/extensions/string"
Generally speaking, modules that have side effects are harder to debug and can cause compatibility issues, and should be avoided if possible.
Migration
To assist in assessing this RFC in real-world applications, and to help upgrade apps should this RFC be accepted and implemented, your author has provided an automatic migration utility, or "codemod":
To run the codemod, cd
into an existing Ember app and run the following commands.
npm install ember-modules-codemod -g
ember-modules-codemod
Note: The codemod currently requires Node 6 or later to run.
This codemod uses jscodeshift
to
update an Ember application in-place to the module syntax proposed in this RFC.
It can update apps that use the global Ember
, and will eventually also support
apps using ember-cli-shims.
Make sure you save any changes in your app before running the codemod, because it modifies files in place. Obviously, because this RFC is speculative, your app will not function after applying this codemod. For now, the codemod is only useful for assessing how this proposal looks in real-world applications.
For example, it will rewrite code that looks like this:
import Ember from 'ember';
export default Ember.Component.extend({
isAnimal: Ember.computed.or('isDog', 'isCat')
});
Into this:
import Component from '@ember/component';
import { or } from '@ember/object/computed';
export default Component.extend({
isAnimal: or('isDog', 'isCat')
});
For more information, see the README.
How We Teach This
This RFC makes changes to one of the most foundational (and historically stable) concepts in Ember: how you access framework code. Because of that, it is hard to overstate the impact these changes will have. We need to proceed carefully to avoid confusion and churn.
It is possible that the work required to update the documentation and other learning materials will be significantly more than the work required to do the actual implementation. That means we need to start getting ready now, so that when the code changes are ready, it is not blocked by a big documentation effort.
That said, we do have the advantage of the new modules being "just JavaScript." We can lean heavily on the greater JavaScript community's learning materials, and any teaching we do has the benefit of being transferable and not an "Ember-only" skill.
Documentation Examples
Examples in the Getting Started tutorial, guides and API docs will need to be updated to the new module syntax.
Probably the most efficient and least painful way to do this would be to write a tool that can extract code snippets from Markdown files and run the migrator on them, then replace the extracted code with the updated version. For the API docs, this tool would need to be able to handle Markdown embedded in JSDoc-style documentation.
The benefit of this approach is that, once we have verified the script works reliably, we can wait until the last possible moment to make the switch. If we attempt to update everything by hand, the duration and tediousness of that process will likely take out an effective "lock" on the documentation code base, where people will put off making big changes because of the potential for merge conflicts.
Generators
Generators are used by new users to help them get a handle on the framework, and by experienced users to avoid typing repetitive boilerplate. We need to ensure that the generators that ship with Ember are updated to use modules as soon as they are ready. The recent work by the Ember CLI team to ship generators with the Ember package itself, rather than Ember CLI, should make this relatively painless.
API Documentation
Our API documentation has long been a source of frustration, because the laundry list of (often rarely used or internal) classes makes Ember feel far more overwhelming than it really is.
The shift to modules gives us a good opportunity to rethink the presentation of our API documentation. Instead of the imposing mono-list, we should group the API documentation by package–which, conveniently in this proposal, means they will also be grouped by area of functionality.
We should investigate the broader ecosystem to see if there is a good tool that generates package-oriented documentation for JavaScript projects. If not, we may wish to adapt an existing tool to do so.
Explaining the Migration
Once the guides and API documentation are updated, modules should be straightforward for new learners—indeed, more and more new learners are starting with JavaScript modules as the baseline.
The most challenging aspect of teaching the new modules API, counterintuitively, will likely be existing users. In particular, for changes that touch nearly every file, most teams working on large apps cannot pause work for a week to implement the change.
Our focus needs to be:
- Communicating clearly that the existing global build will work for the foreseeable future.
- Making clear the file size benefits of moving to modules.
- Building robust tooling that allows even large apps to migrate in a day or two, not a week.
It is important to frame the module transition as a carrot, not a stick. We should avoid dire warnings or deprecation notices. Instead, we should provide good reporting when doing Ember CLI builds. If an app is compiled in globals mode, we can offer suggestions for how to reduce the file size, providing a helpful pointer to the modules migration guide. This will make the transition feel less like churn and more like an optimization opportunity that developers can take advantage of when they have the time or resources.
Addons
One pitfall is that a single use of the Ember
global means we have to
include the entire framework. That means that a developer could migrate their
entire app to modules, but a single old addon that uses the Ember globals will
negate the benefits.
This requires a two-pronged strategy:
- Tight integration into Ember CLI
- Good reporting to make it obvious when a fallback to globals mode occurs, and which addons/files are causing it.
- An opt-in mode to prohibit globals mode. Installing an incompatible addon would produce an error.
- Community outreach and pull requests to help authors update addons.
Drawbacks
Complexity
There is something elegantly simple about a single Ember
global that contains
everything. Introducing multiple packages means you don't just have to know what
you need—you also need to know where to import it from.
JavaScript module syntax is also something not everyone will be familiar with, given its newness. However, this is something we must deal with anyway because module syntax is already in use within apps.
Module Churn
The ember-cli-shims
package is already included by default in new Ember apps,
and is in fairly common usage. Many developers are already familiar with its
API. This drawback can be at least partially mitigated by the automated
migration process, which will be easily applied to existing shimmed
apps.
Scoped Packages Are an Unknown Quantity
This proposal relies on scoped packages. Despite being released over a year ago, scoped packages are not always well supported.
For example, scoped packages currently wreak havoc on Yarn. Until very recently, the npmjs.com search did not include scoped packages. Generally speaking, there will be a long-tail of tools in the ecosystem that will choke on scoped packages.
That said, Angular 2 is distributed under the @angular
scope, and TypeScript
recently adopted the @types
scope for publishing TypeScript typings to npm.
The popularity of both of these should drive compatibility. Despite this, we can
expect similar compatibility issues for some time.
Nested Modules
To satisfy the Classes are Default Exports rule,
this RFC proposes the use of nested modules. That is, a module name may contain
an additional path segment beyond the package name. For example,
@ember/object/observers
is a nested module, while @ember/object
is not.
In the Node/CommonJS world, nested modules are unusual but not unheard of. For
example, Lodash offers a functional programming
style accessed by calling
require('lodash/fp')
.
There are two drawbacks associated with nested modules:
- Because they are uncommon, developers may be confused by the syntax.
- Because they allow you to "reach in" to the package for an arbitrary file, encouraging the end user to use nested modules may inadvertently also encourage them to access private modules, thinking they are public.
The first issue is surmountable with education, good reference documentation, and good tools to help guide developers in the right direction. That this style is uncommon in the Node ecosystem seems to be more a function of dogma than any technical shortcoming of nested modules.
To ensure that developers don't inadvertently access private modules, we have two good options:
- Package modules in such a way that private modules cannot be accessed.
- Take a page from Ember Data and put all private modules in a
-private
directory, hopefully making it clear accessing this module is not playing by the rules.
We could avoid using this uncommon style by hoisting nested modules up to their
own package. For example, @ember/object/observers
could become
@ember/observers
or @ember/object-observers
. However, because I could not
find a strong technical reason against it, and because having more packages is
in tension with the explicit goal to make Ember feel less
overwhelming, I decided it was worth the small cost.
Alternatives
ember-
prefix
One alternative to the @ember
scope is to use the ember-
prefix. This avoids
the drawbacks around scoped packages described above. However, they would be
indistinguishable from the large number of community packages that begin with
ember-
.
Everything is a Named Export
This proposal argues that classes should be a module's default export, and any
utility functions should be a named export. That means you can never have more
than one class per module, and that means, inherently, more import
statements than a system where multiple classes can live in one module.
Additionally, in cases where there is not a clear "primary" class, this can feel a little awkward:
import Route from "@ember/routing/route";
import Router from "@ember/routing/router";
One commonly proposed alternative is to say that classes become named exports, and default exports are not used at all. The above example would become:
import { Route, Router } from "@ember/routing";
In this case, classes are distinguished by being capitalized, rather than by being a default export.
There is one major change coming to JavaScript and Ember that, your author believes, deals a fatal blow to this approach: decorators.
If you're unfamiliar with decorators, see Addy Osmani's great overview. Decorators provide a mechanism for adding declarative annotations to classes, methods, properties and functions.
For example, Robert Jackson has an experimental library for using decorators to annotate computed properties in a class. Something like this will probably make its way into Ember in the future:
import EmberObject, { computed } from "@ember/object";
export default class Cat extends EmberObject {
@computed("hairLength")
isDomesticShortHair(hairLength) {
return hairLength < 3;
}
}
Most decorators are tightly coupled to a particular class because they configure some aspect of behavior that is only relevant to that class. If every decorator and every class share a namespace, it is hard to identify which go with each other.
import { Router, Route, resource, model, location, inject, queryParam } from "@ember/routing";
Can you tell me which of these decorators goes with which class?
And this import is getting so long, you'd probably be tempted to break it up into multiple lines anyway, so it's not clear that it's actually a win over separate imports.
Contrast this with the same thing expressed using the rules in this RFC:
import Router, { resource, location } from "@ember/routing/router";
import Route, { model, inject, queryParam } from "@ember/routing/route";
Here, the decorators are clearly tied to their class. And it's far nicer from a refactoring perspective: if you delete a class from a file, you then delete a single line from your imports.
Contrast that with making fiddly edits to a long list of named exports unrelated to each other.
Unresolved Questions
Intimate APIs
How much do we want to provide module API for so-called "intimate APIs"—technically private, but in widespread use?
Backwards Compatibility for Addons
How do we provide an API for addons to use modules but fall back to globals mode in older versions of Ember? We should ensure that, at minimum, addons can continue to support LTS releases. At the same time, it's critical that adding an addon doesn't opt your entire application back in to the entire framework.
Because there is a lot of implementation-specific detail to get right here, and because it doesn't otherwise block landing this module naming RFC, the final design of API for addon authors should be broken out into a separate RFC.
Distribution
In practice, how do we ship this code to end users of Ember CLI?
When building client-side apps, it's very important to avoid duplicate dependencies, which can quickly cause file size to balloon out of control.
Unfortunately, npm@3's de-duping is so naïve that it's likely that users would end up in dependency hell if we shipped the framework as separate npm packages. There's no good way to ship dependencies in version lockstep and feel confident that they will reliably be de-duped.
Until Yarn usage is more widespread, and to eliminate significant complexity in the first iteration, it probably makes sense for the first phase of implementation to continue shipping a single npm package that Ember CLI apps can depend on. This gives us atomic updates and makes sure you never have one piece of the framework interacting with a different piece that is inadvertently three versions old.
What this means is that, rather than shipping @ember/object
on npm, we'd ship
a single ember-source
(or something) package that includes the entire
framework. At build time, the Ember build process would virtually map the
@ember/object
package to the right file inside ember-source
. In essence, all
of the benefits of smaller bundles without the boiling hellbroth of managing
dependencies.
That said, because this RFC is designed with an eye towards eventually publishing each package to npm individually, we will have that option available to us in the future once we determine that we can do so without causing lots of pain.
Addenda
(Ed. note: These tables are automatically generated from the scripts in the codemod repository.)
Addendum 1 - Table of Module Names and Exports by Global
Before | After |
---|---|
Ember.$ |
import $ from "jquery" |
Ember.A |
import { A } from "@ember/array" |
Ember.Application |
import Application from "@ember/application" |
Ember.Array |
import EmberArray from "@ember/array" |
Ember.ArrayProxy |
import ArrayProxy from "@ember/array/proxy" |
Ember.AutoLocation |
import AutoLocation from "@ember/routing/auto-location" |
Ember.Checkbox |
import Checkbox from "@ember/component/checkbox" |
Ember.Component |
import Component from "@ember/component" |
Ember.ContainerDebugAdapter |
import ContainerDebugAdapter from "@ember/debug/container-debug-adapter" |
Ember.Controller |
import Controller from "@ember/controller" |
Ember.DataAdapter |
import DataAdapter from "@ember/debug/data-adapter" |
Ember.DefaultResolver |
import GlobalsResolver from "@ember/application/globals-resolver" |
Ember.Enumerable |
import Enumerable from "@ember/enumerable" |
Ember.Evented |
import Evented from "@ember/object/evented" |
Ember.HashLocation |
import HashLocation from "@ember/routing/hash-location" |
Ember.Helper |
import Helper from "@ember/component/helper" |
Ember.Helper.helper |
import { helper } from "@ember/component/helper" |
Ember.HistoryLocation |
import HistoryLocation from "@ember/routing/history-location" |
Ember.LinkComponent |
import LinkComponent from "@ember/routing/link-component" |
Ember.Location |
import Location from "@ember/routing/location" |
Ember.Map |
import EmberMap from "@ember/map" |
Ember.MapWithDefault |
import MapWithDefault from "@ember/map/with-default" |
Ember.Mixin |
import Mixin from "@ember/object/mixin" |
Ember.MutableArray |
import MutableArray from "@ember/array/mutable" |
Ember.NoneLocation |
import NoneLocation from "@ember/routing/none-location" |
Ember.Object |
import EmberObject from "@ember/object" |
Ember.RSVP |
import RSVP from "rsvp" |
Ember.Resolver |
import Resolver from "@ember/application/resolver" |
Ember.Route |
import Route from "@ember/routing/route" |
Ember.Router |
import Router from "@ember/routing/router" |
Ember.Service |
import Service from "@ember/service" |
Ember.String.camelize |
import { camelize } from "@ember/string" |
Ember.String.capitalize |
import { capitalize } from "@ember/string" |
Ember.String.classify |
import { classify } from "@ember/string" |
Ember.String.dasherize |
import { dasherize } from "@ember/string" |
Ember.String.decamelize |
import { decamelize } from "@ember/string" |
Ember.String.fmt |
import { fmt } from "@ember/string" |
Ember.String.htmlSafe |
import { htmlSafe } from "@ember/string" |
Ember.String.loc |
import { loc } from "@ember/string" |
Ember.String.underscore |
import { underscore } from "@ember/string" |
Ember.String.w |
import { w } from "@ember/string" |
Ember.TextArea |
import TextArea from "@ember/component/text-area" |
Ember.TextField |
import TextField from "@ember/component/text-field" |
Ember.addListener |
import { addListener } from "@ember/object/events" |
Ember.addObserver |
import { addObserver } from "@ember/object/observers" |
Ember.aliasMethod |
import { aliasMethod } from "@ember/object" |
Ember.assert |
import { assert } from "@ember/debug" |
Ember.assign |
import { assign } from "@ember/polyfills" |
Ember.cacheFor |
import { cacheFor } from "@ember/object/internals" |
Ember.compare |
import { compare } from "@ember/utils" |
Ember.computed |
import { computed } from "@ember/object" |
Ember.computed.alias |
import { alias } from "@ember/object/computed" |
Ember.computed.and |
import { and } from "@ember/object/computed" |
Ember.computed.bool |
import { bool } from "@ember/object/computed" |
Ember.computed.collect |
import { collect } from "@ember/object/computed" |
Ember.computed.deprecatingAlias |
import { deprecatingAlias } from "@ember/object/computed" |
Ember.computed.empty |
import { empty } from "@ember/object/computed" |
Ember.computed.equal |
import { equal } from "@ember/object/computed" |
Ember.computed.filter |
import { filter } from "@ember/object/computed" |
Ember.computed.filterBy |
import { filterBy } from "@ember/object/computed" |
Ember.computed.filterProperty |
import { filterProperty } from "@ember/object/computed" |
Ember.computed.gt |
import { gt } from "@ember/object/computed" |
Ember.computed.gte |
import { gte } from "@ember/object/computed" |
Ember.computed.intersect |
import { intersect } from "@ember/object/computed" |
Ember.computed.lt |
import { lt } from "@ember/object/computed" |
Ember.computed.lte |
import { lte } from "@ember/object/computed" |
Ember.computed.map |
import { map } from "@ember/object/computed" |
Ember.computed.mapBy |
import { mapBy } from "@ember/object/computed" |
Ember.computed.mapProperty |
import { mapProperty } from "@ember/object/computed" |
Ember.computed.match |
import { match } from "@ember/object/computed" |
Ember.computed.max |
import { max } from "@ember/object/computed" |
Ember.computed.min |
import { min } from "@ember/object/computed" |
Ember.computed.none |
import { none } from "@ember/object/computed" |
Ember.computed.not |
import { not } from "@ember/object/computed" |
Ember.computed.notEmpty |
import { notEmpty } from "@ember/object/computed" |
Ember.computed.oneWay |
import { oneWay } from "@ember/object/computed" |
Ember.computed.or |
import { or } from "@ember/object/computed" |
Ember.computed.readOnly |
import { readOnly } from "@ember/object/computed" |
Ember.computed.reads |
import { reads } from "@ember/object/computed" |
Ember.computed.setDiff |
import { setDiff } from "@ember/object/computed" |
Ember.computed.sort |
import { sort } from "@ember/object/computed" |
Ember.computed.sum |
import { sum } from "@ember/object/computed" |
Ember.computed.union |
import { union } from "@ember/object/computed" |
Ember.computed.uniq |
import { uniq } from "@ember/object/computed" |
Ember.copy |
import { copy } from "@ember/object/internals" |
Ember.create |
import { create } from "@ember/polyfills" |
Ember.debug |
import { debug } from "@ember/debug" |
Ember.deprecate |
import { deprecate } from "@ember/application/deprecations" |
Ember.deprecateFunc |
import { deprecateFunc } from "@ember/application/deprecations" |
Ember.get |
import { get } from "@ember/object" |
Ember.getOwner |
import { getOwner } from "@ember/application" |
Ember.getProperties |
import { getProperties } from "@ember/object" |
Ember.guidFor |
import { guidFor } from "@ember/object/internals" |
Ember.inject.controller |
import { inject } from "@ember/controller" |
Ember.inject.service |
import { inject } from "@ember/service" |
Ember.inspect |
import { inspect } from "@ember/debug" |
Ember.instrument |
import { instrument } from "@ember/instrumentation" |
Ember.isArray |
import { isArray } from "@ember/array" |
Ember.isBlank |
import { isBlank } from "@ember/utils" |
Ember.isEmpty |
import { isEmpty } from "@ember/utils" |
Ember.isEqual |
import { isEqual } from "@ember/utils" |
Ember.isNone |
import { isNone } from "@ember/utils" |
Ember.isPresent |
import { isPresent } from "@ember/utils" |
Ember.keys |
import { keys } from "@ember/polyfills" |
Ember.makeArray |
import { makeArray } from "@ember/array" |
Ember.observer |
import { observer } from "@ember/object" |
Ember.on |
import { on } from "@ember/object/evented" |
Ember.onLoad |
import { onLoad } from "@ember/application" |
Ember.platform.defineProperty |
import { defineProperty } from "@ember/polyfills" |
Ember.platform.hasPropertyAccessors |
import { hasPropertyAccessors } from "@ember/polyfills" |
Ember.removeListener |
import { removeListener } from "@ember/object/events" |
Ember.removeObserver |
import { removeObserver } from "@ember/object/observers" |
Ember.reset |
import { reset } from "@ember/instrumentation" |
Ember.run |
import { run } from "@ember/runloop" |
Ember.run.begin |
import { begin } from "@ember/runloop" |
Ember.run.bind |
import { bind } from "@ember/runloop" |
Ember.run.cancel |
import { cancel } from "@ember/runloop" |
Ember.run.debounce |
import { debounce } from "@ember/runloop" |
Ember.run.end |
import { end } from "@ember/runloop" |
Ember.run.join |
import { join } from "@ember/runloop" |
Ember.run.later |
import { later } from "@ember/runloop" |
Ember.run.next |
import { next } from "@ember/runloop" |
Ember.run.once |
import { once } from "@ember/runloop" |
Ember.run.schedule |
import { schedule } from "@ember/runloop" |
Ember.run.scheduleOnce |
import { scheduleOnce } from "@ember/runloop" |
Ember.run.throttle |
import { throttle } from "@ember/runloop" |
Ember.runInDebug |
import { runInDebug } from "@ember/debug" |
Ember.runLoadHooks |
import { runLoadHooks } from "@ember/application" |
Ember.sendEvent |
import { sendEvent } from "@ember/object/events" |
Ember.set |
import { set } from "@ember/object" |
Ember.setOwner |
import { setOwner } from "@ember/application" |
Ember.setProperties |
import { setProperties } from "@ember/object" |
Ember.subscribe |
import { subscribe } from "@ember/instrumentation" |
Ember.tryInvoke |
import { tryInvoke } from "@ember/utils" |
Ember.trySet |
import { trySet } from "@ember/object" |
Ember.typeOf |
import { typeOf } from "@ember/utils" |
Ember.unsubscribe |
import { unsubscribe } from "@ember/instrumentation" |
Ember.warn |
import { warn } from "@ember/debug" |
Addendum 2 - Table of Module Names and Exports by Package
Each package is sorted by module name, then export name.
@ember/application
Module | Global |
---|---|
import Application from "@ember/application" |
Ember.Application |
import { getOwner } from "@ember/application" |
Ember.getOwner |
import { onLoad } from "@ember/application" |
Ember.onLoad |
import { runLoadHooks } from "@ember/application" |
Ember.runLoadHooks |
import { setOwner } from "@ember/application" |
Ember.setOwner |
import { deprecate } from "@ember/application/deprecations" |
Ember.deprecate |
import { deprecateFunc } from "@ember/application/deprecations" |
Ember.deprecateFunc |
import GlobalsResolver from "@ember/application/globals-resolver" |
Ember.DefaultResolver |
import Resolver from "@ember/application/resolver" |
Ember.Resolver |
@ember/array
Module | Global |
---|---|
import EmberArray from "@ember/array" |
Ember.Array |
import { A } from "@ember/array" |
Ember.A |
import { isArray } from "@ember/array" |
Ember.isArray |
import { makeArray } from "@ember/array" |
Ember.makeArray |
import MutableArray from "@ember/array/mutable" |
Ember.MutableArray |
import ArrayProxy from "@ember/array/proxy" |
Ember.ArrayProxy |
@ember/component
Module | Global |
---|---|
import Component from "@ember/component" |
Ember.Component |
import Checkbox from "@ember/component/checkbox" |
Ember.Checkbox |
import Helper from "@ember/component/helper" |
Ember.Helper |
import { helper } from "@ember/component/helper" |
Ember.Helper.helper |
import TextArea from "@ember/component/text-area" |
Ember.TextArea |
import TextField from "@ember/component/text-field" |
Ember.TextField |
@ember/controller
Module | Global |
---|---|
import Controller from "@ember/controller" |
Ember.Controller |
import { inject } from "@ember/controller" |
Ember.inject.controller |
@ember/debug
Module | Global |
---|---|
import { assert } from "@ember/debug" |
Ember.assert |
import { debug } from "@ember/debug" |
Ember.debug |
import { inspect } from "@ember/debug" |
Ember.inspect |
import { runInDebug } from "@ember/debug" |
Ember.runInDebug |
import { warn } from "@ember/debug" |
Ember.warn |
import ContainerDebugAdapter from "@ember/debug/container-debug-adapter" |
Ember.ContainerDebugAdapter |
import DataAdapter from "@ember/debug/data-adapter" |
Ember.DataAdapter |
@ember/enumerable
Module | Global |
---|---|
import Enumerable from "@ember/enumerable" |
Ember.Enumerable |
@ember/instrumentation
Module | Global |
---|---|
import { instrument } from "@ember/instrumentation" |
Ember.instrument |
import { reset } from "@ember/instrumentation" |
Ember.reset |
import { subscribe } from "@ember/instrumentation" |
Ember.subscribe |
import { unsubscribe } from "@ember/instrumentation" |
Ember.unsubscribe |
@ember/map
Module | Global |
---|---|
import EmberMap from "@ember/map" |
Ember.Map |
import MapWithDefault from "@ember/map/with-default" |
Ember.MapWithDefault |
@ember/object
Module | Global |
---|---|
import EmberObject from "@ember/object" |
Ember.Object |
import { aliasMethod } from "@ember/object" |
Ember.aliasMethod |
import { computed } from "@ember/object" |
Ember.computed |
import { get } from "@ember/object" |
Ember.get |
import { getProperties } from "@ember/object" |
Ember.getProperties |
import { observer } from "@ember/object" |
Ember.observer |
import { set } from "@ember/object" |
Ember.set |
import { setProperties } from "@ember/object" |
Ember.setProperties |
import { trySet } from "@ember/object" |
Ember.trySet |
import { alias } from "@ember/object/computed" |
Ember.computed.alias |
import { and } from "@ember/object/computed" |
Ember.computed.and |
import { bool } from "@ember/object/computed" |
Ember.computed.bool |
import { collect } from "@ember/object/computed" |
Ember.computed.collect |
import { deprecatingAlias } from "@ember/object/computed" |
Ember.computed.deprecatingAlias |
import { empty } from "@ember/object/computed" |
Ember.computed.empty |
import { equal } from "@ember/object/computed" |
Ember.computed.equal |
import { filter } from "@ember/object/computed" |
Ember.computed.filter |
import { filterBy } from "@ember/object/computed" |
Ember.computed.filterBy |
import { filterProperty } from "@ember/object/computed" |
Ember.computed.filterProperty |
import { gt } from "@ember/object/computed" |
Ember.computed.gt |
import { gte } from "@ember/object/computed" |
Ember.computed.gte |
import { intersect } from "@ember/object/computed" |
Ember.computed.intersect |
import { lt } from "@ember/object/computed" |
Ember.computed.lt |
import { lte } from "@ember/object/computed" |
Ember.computed.lte |
import { map } from "@ember/object/computed" |
Ember.computed.map |
import { mapBy } from "@ember/object/computed" |
Ember.computed.mapBy |
import { mapProperty } from "@ember/object/computed" |
Ember.computed.mapProperty |
import { match } from "@ember/object/computed" |
Ember.computed.match |
import { max } from "@ember/object/computed" |
Ember.computed.max |
import { min } from "@ember/object/computed" |
Ember.computed.min |
import { none } from "@ember/object/computed" |
Ember.computed.none |
import { not } from "@ember/object/computed" |
Ember.computed.not |
import { notEmpty } from "@ember/object/computed" |
Ember.computed.notEmpty |
import { oneWay } from "@ember/object/computed" |
Ember.computed.oneWay |
import { or } from "@ember/object/computed" |
Ember.computed.or |
import { readOnly } from "@ember/object/computed" |
Ember.computed.readOnly |
import { reads } from "@ember/object/computed" |
Ember.computed.reads |
import { setDiff } from "@ember/object/computed" |
Ember.computed.setDiff |
import { sort } from "@ember/object/computed" |
Ember.computed.sort |
import { sum } from "@ember/object/computed" |
Ember.computed.sum |
import { union } from "@ember/object/computed" |
Ember.computed.union |
import { uniq } from "@ember/object/computed" |
Ember.computed.uniq |
import Evented from "@ember/object/evented" |
Ember.Evented |
import { on } from "@ember/object/evented" |
Ember.on |
import { addListener } from "@ember/object/events" |
Ember.addListener |
import { removeListener } from "@ember/object/events" |
Ember.removeListener |
import { sendEvent } from "@ember/object/events" |
Ember.sendEvent |
import { cacheFor } from "@ember/object/internals" |
Ember.cacheFor |
import { copy } from "@ember/object/internals" |
Ember.copy |
import { guidFor } from "@ember/object/internals" |
Ember.guidFor |
import Mixin from "@ember/object/mixin" |
Ember.Mixin |
import { addObserver } from "@ember/object/observers" |
Ember.addObserver |
import { removeObserver } from "@ember/object/observers" |
Ember.removeObserver |
@ember/polyfills
Module | Global |
---|---|
import { assign } from "@ember/polyfills" |
Ember.assign |
import { create } from "@ember/polyfills" |
Ember.create |
import { defineProperty } from "@ember/polyfills" |
Ember.platform.defineProperty |
import { hasPropertyAccessors } from "@ember/polyfills" |
Ember.platform.hasPropertyAccessors |
import { keys } from "@ember/polyfills" |
Ember.keys |
@ember/routing
Module | Global |
---|---|
import AutoLocation from "@ember/routing/auto-location" |
Ember.AutoLocation |
import HashLocation from "@ember/routing/hash-location" |
Ember.HashLocation |
import HistoryLocation from "@ember/routing/history-location" |
Ember.HistoryLocation |
import LinkComponent from "@ember/routing/link-component" |
Ember.LinkComponent |
import Location from "@ember/routing/location" |
Ember.Location |
import NoneLocation from "@ember/routing/none-location" |
Ember.NoneLocation |
import Route from "@ember/routing/route" |
Ember.Route |
import Router from "@ember/routing/router" |
Ember.Router |
@ember/runloop
Module | Global |
---|---|
import { begin } from "@ember/runloop" |
Ember.run.begin |
import { bind } from "@ember/runloop" |
Ember.run.bind |
import { cancel } from "@ember/runloop" |
Ember.run.cancel |
import { debounce } from "@ember/runloop" |
Ember.run.debounce |
import { end } from "@ember/runloop" |
Ember.run.end |
import { join } from "@ember/runloop" |
Ember.run.join |
import { later } from "@ember/runloop" |
Ember.run.later |
import { next } from "@ember/runloop" |
Ember.run.next |
import { once } from "@ember/runloop" |
Ember.run.once |
import { run } from "@ember/runloop" |
Ember.run |
import { schedule } from "@ember/runloop" |
Ember.run.schedule |
import { scheduleOnce } from "@ember/runloop" |
Ember.run.scheduleOnce |
import { throttle } from "@ember/runloop" |
Ember.run.throttle |
@ember/service
Module | Global |
---|---|
import Service from "@ember/service" |
Ember.Service |
import { inject } from "@ember/service" |
Ember.inject.service |
@ember/string
Module | Global |
---|---|
import { camelize } from "@ember/string" |
Ember.String.camelize |
import { capitalize } from "@ember/string" |
Ember.String.capitalize |
import { classify } from "@ember/string" |
Ember.String.classify |
import { dasherize } from "@ember/string" |
Ember.String.dasherize |
import { decamelize } from "@ember/string" |
Ember.String.decamelize |
import { fmt } from "@ember/string" |
Ember.String.fmt |
import { htmlSafe } from "@ember/string" |
Ember.String.htmlSafe |
import { loc } from "@ember/string" |
Ember.String.loc |
import { underscore } from "@ember/string" |
Ember.String.underscore |
import { w } from "@ember/string" |
Ember.String.w |
@ember/utils
Module | Global |
---|---|
import { compare } from "@ember/utils" |
Ember.compare |
import { isBlank } from "@ember/utils" |
Ember.isBlank |
import { isEmpty } from "@ember/utils" |
Ember.isEmpty |
import { isNone } from "@ember/utils" |
Ember.isNone |
import { isPresent } from "@ember/utils" |
Ember.isPresent |
import { tryInvoke } from "@ember/utils" |
Ember.tryInvoke |
import { typeOf } from "@ember/utils" |
Ember.typeOf |
jquery
Module | Global |
---|---|
import $ from "jquery" |
Ember.$ |
rsvp
Module | Global |
---|---|
import RSVP from "rsvp" |
Ember.RSVP |
Addendum 3 - Table of Modules with Side Effects
Module | Description |
---|---|
import "@ember/extensions" |
Adds all of Ember's prototype extensions. |
import "@ember/extensions/string" |
Adds just String prototype extensions. |
import "@ember/extensions/array" |
Adds just Array prototype extensions. |
import "@ember/extensions/function" |
Adds just Function prototype extensions. |