EmberCamp Module Unification Update
Wow! This takes me back! Please check the date this post was authored, as it may no longer be relevant in a modern context.
Yesterday morning at EmberCamp London I gave a talk on Glimmer, Ember.js and the relationship between the projects.
As part of that talk I covered the latest news on Ember’s new application filesystem layout, a project we refer to as “module unification”.
As of a few days ago, all the code needed to run a module unification app has landed in the various requisite repositories. In this post I’ll give you a little background on the work we’re doing and tell you how to start a new app or migrate an existing one.
Reviewing the plan
As part of creating the Glimmer application library, Dan Gebhardt created the @glimmer/resolver package. This library implements the resolution rules for module unification based on a configuration. For example here is the resolver config for Glimmer apps.
How can we leverage the @glimmer/resolver functionality in an Ember app? We’ve chosen to approach its adoption in two phases.
- In phase 1 @glimmer/resolver will be wrapped behind the existing ember-resolver API. The ember-resolver can provide its own configuration of @glimmer/resolver appropriate for Ember apps.
- In phase 2, @glimmer/di will replace the Ember container. At this point, @glimmer/resolver’s API would become the new public Ember resolver API. Of course to land phase 2 we need a significant RFC.
Phase 1 is the part of this work that is close to feature complete today. It includes:
- The
MODULE_UNIFICATION
feature flag in Ember-CLI. - The
ember-module-unification
feature flag in Ember.js. - The
EMBER_RESOLVER_MODULE_UNIFICATION
feature flag in ember-resolver.
Let’s look at how to use module unification today.
Creating a new app
To create a new Ember application using module unification run the following (presuming you have Ember-CLI >2.14.0 installed):
ember new my-app -b ember-module-unification-blueprint
This will create a new application all ready to go. It uses the ember-module-unification-blueprint package to set up the app.
For a new app, that’s basically all you need to do. If you attempt to use an
addon with an app/
directory, you may need to use some of the addon support
steps I’ll describe in the next section: How to migrate an existing app.
Migrating an existing app
The Ember project continues to value migration paths to new features for existing apps. The ember-module-migrator will handle moving your files from the old file layout to the new one, but there are a few practical details you should know about if you want to run an app today.
Prerequisites to setup
Before going any further, I suggest upgrading the following libraries in your app:
- ember-resolver >4.3.0
- Ember.js Canary (which means using Ember via bower)
- Ember-CLI Canary
If you use npm, this will look something like:
npm install ember-resolver@^4.3.0 ember-cli@github:ember-cli/ember-cli --save-dev
npm uninstall ember-source --save
bower install --save components/ember#canary
Additionally components with /
in the name are explicitly disallowed by
the rules of module unification. To address this, remove any component nesting.
For example the following would be invalid:
{{foo/bar-baz}}
{{foo-bar/baz}}
{{component 'foo/bar-baz'}}
{{component someComponentNameWithASlash}}
If you’re groaning because your application will lose some nesting that was nice for organization, don’t worry too much. The ember-module-migrator is smart enough to identify components and helpers that are only used in one place and make those into local lookup components. For example if you have files like the following:
app/components/my-form.js
app/components/my-input.js
app/templates/components/my-form.hbs
app/templates/components/my-input.hbs
And {{my-input}}
is only used in my-form.hbs
, the migrated app will
contain:
src/ui/components/my-form/-components/my-input/component.js
src/ui/components/my-form/-components/my-input/template.hbs
src/ui/components/my-form/component.js
src/ui/components/my-form/template.hbs
This migration behavior means any loss of organization should be fairly temporary.
Running the migrator
Migrate the files in your app by running the ember-module-migrator:
npm install -g ember-module-migrator jscodeshift
cd your/project/path
ember-module-migrator
After this step, your app/
directory should now be gone and a src/
directory
created in its place.
Several files must be updated at this point for your app to boot, most
specifically the src/resolver.js
file and src/main.js
file. The easiest way
to move forward is to run the
ember-module-unification-blueprint
and review the changes as you would any addon. Run:
ember init -b ember-module-unification-blueprint
Many apps should boot at this point. There are three caveats you should consider before opening an issue if your app does not boot:
{{component}}
helper usage
Note that the module migrator understands .hbs
templates, but is limited in
the static analysis it can perform on JavaScript files.
Any usage of the dynamic
{{component}}
helper should be audited for correctness after migrating.
Addons providing an app/
tree
There isn’t yet a migration path for addons to use module unification. This is
something looming as a next step for our efforts. As such, it isn’t surprising
if your app uses an addon that provides an app/
tree and expects you to
resolve components, services, and initializers in it.
To enable resolution of files in app/
as a fallback from src/
, edit
src/resolver.js
to use the fallback resolver. Change the import of:
import Resolver from 'ember-resolver/resolvers/glimmer-wrapper';
to
import Resolver from 'ember-resolver/resolvers/fallback';
To load initializers from app/
, you should additionally edit src/main.js
to
include the following:
/* Add this after the existing `loadInitializers` call. */
loadInitializers(App, config.modulePrefix);
At this point many addons with an app/
directory should work.
Custom collections and types
The classic ember resolver allowed you to create a file named
app/animals/cat.js
and then resolve it in the container as 'animal:cat'
.
In application code or addon app/
code, this may exist in your codebase.
Within the rules of module unification all factory collections and types must be explicitly defined. You can add types by directly mutating the module configuration passed to the resolver.
For example if you have a file at:
src/component-managers/glimmer.js
You would allow it to be resolved as 'component-manager:glimmer'
by adding
this config in src/resolver.js
:
let moduleConfig = buildResolverConfig(config.modulePrefix);
/*
* If your application has custom types and collections, modify moduleConfig
here
* to add support for them.
*/
moduleConfig.types['component-manager'] = {
definitiveCollection: 'component-managers'
};
moduleConfig.collections['component-managers'] = {
types: ['component-manager'],
privateCollections: ['utils'],
defaultType: 'component-manager'
};
If you are using the fallback resolver, you may need to define a type just so
the module unification lookup can fail before the classic resolver takes over.
Another cause for this change would be your app or an addon manually registering
a factory with an unknown type and expecting it to look up successfully (an
initializer calling application.register('animal:cat', Cat)
).
For example if you have a file (in your app/
or in an addon’s app/
):
app/animals/cat.js
You must define the type and some dummy collection config in src/resolver.js
:
let moduleConfig = buildResolverConfig(config.modulePrefix);
/*
* If your application has custom types and collections, modify moduleConfig
here
* to add support for them.
*/
moduleConfig.types['animal'] = {
definitiveCollection: 'animals'
};
moduleConfig.collections['animals'] = {
types: ['animal']
};
For more information on what values are appropriate in this configuration, see the types definition in @glimmer/resolver and Ember’s default configuration in ember-resolver.
Next steps
Feedback on using module unification in its current state is essential for work on the feature to continue. At this point our biggest known issue is related to the story around addons.
Small issues we’re already tracking are:
- ember-cli/ember-cli#7215: [FEATURE ember-module-unification] styles in app/ when using src/
- rwjblue/ember-module-migrator#53: Component helper usage should warn
- rwjblue/ember-module-migrator#61: Component with - in the name should not be converted
- Comment on
emberjs/ember.js#15350: Auto-generated routes with
{{link-to}}
cause an unexpected error.
If you run into one of these issues please add a comment. If you run into
something unexpected open an issue. If you have an issue but aren’t sure where to
file it, please don’t hesitate to join us in #st-module-unification
on the
Ember.js Community Slack
and we can help you out.
Past this work, we’re eager to start on phase 2 (replacing Ember’s container with @glimmer/di) in parallel. The major blocker on that work has been the test conversation epic emberjs/ember.js#15058, however that work is nearly complete.
Interested in supporting our work on module unification?
Contact us at 201 Created
and let’s chat. If you want to follow our progress or have a question
about module unification, join us in #st-module-unification
on the
Ember.js Community Slack.