This Ember addon implements the functionality described in the Ember Engines RFC. Engines allow multiple logical applications to be composed together into a single application from the user's perspective.
This addon must be installed in any ember-cli projects that function as either consumers or providers of engines. The following functionality is supported:
- Routable engines which can be mounted at specific routes in a routing map, and which can contain routes of their own.
- Route-less engines, which can be rendered in a template using the
{{mount}}
keyword. - Sharing of dependencies from parents (applications or other engines) to contained engines. Shared dependencies are currently limited to services and route paths.
The following functionality will soon be supported:
- Lazy loading of engines.
- Route serializer modules that isolate serialization logic from the rest of the route definition.
Support for the following concepts is under consideration:
- Namespaced access to engine resources from applications.
- Sharing of dependencies other than services and route paths.
- Passing configuration attributes from an engine's parent.
This addon should be considered experimental and used with caution.
The master branch of this addon is
being developed against the master branch of Ember.
The ember-application-engines
feature flag must be enabled in order to use
this branch, which should not be considered stable.
The v0.2 branch of this addon is being developed to be compatible with v2.6.x of Ember. This branch should be considered reasonably stable, although it does contain a number of overrides to code in Ember core. Please proceed with caution.
From your Ember CLI project's root directory, run the following:
ember install ember-engines
Install the appropriate version of Ember as noted above.
Engines can be created as separate addon projects or in-repo addons.
Separate addon projects can be created with the addon
command:
ember addon <engine-name>
Note: As described in the RFC, ember-cli will hopefully support an engine
command to get started more easily with engine projects.
In order to create an engine within an existing application's project, run the
in-repo-engine
generator:
ember g in-repo-engine <engine-name>
Don't forget to install ember-engines
and the appropriate version of Ember in
your project, as described above.
Within your engine's root directory, modify index.js
so that your addon
is configured as an engine using the EngineAddon
extension:
/*jshint node:true*/
var EngineAddon = require('ember-engines/lib/engine-addon');
module.exports = EngineAddon.extend({
name: 'ember-blog'
});
Within your engine's config
directory, create a new environment.js
file:
/*jshint node:true*/
'use strict';
module.exports = function(environment) {
var ENV = {
modulePrefix: 'ember-blog',
environment: environment
}
return ENV;
};
Within your engine's addon
directory, add a new engine.js
file:
import Engine from 'ember-engines/engine';
import Resolver from 'ember-engines/resolver'; // <=== IMPORTANT - custom resolver!!!
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
const { modulePrefix } = config;
const Eng = Engine.extend({
modulePrefix,
Resolver
});
loadInitializers(Eng, modulePrefix);
export default Eng;
It's important that modulePrefix
be set in config/environment.js
so that
it can be referenced in addon/engine.js
.
Routable engines should declare their route map in a routes.js
file.
For example:
import buildRoutes from 'ember-engines/routes';
export default buildRoutes(function() {
this.route('new');
this.route('post', { path: 'post/:id' }, function() {
this.route('comments', function() {
this.route('comment', { path: ':id' });
});
});
});
Routable engines interact with the parent application's router as if they are an extension of the parent application. A routable engine's application route will be mounted wherever specified by the parent's route map (its "mountpoint").
Route-less engines should define an engine.js
as described above. Neither
router.js
nor routes.js
should be defined. Route-less engines will be rendered as their application template
(templates/application.hbs
).
Your engine should declare any dependencies that it expects from its parent. Dependencies must be declared in the engine definition.
For example, the following engine requires a store
service from its parent:
import Engine from 'ember-engines/engine';
import Resolver from 'ember-engines/resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
const { modulePrefix } = config;
const Eng = Engine.extend({
modulePrefix,
Resolver,
dependencies: {
services: [
'store'
]
}
});
loadInitializers(Eng, modulePrefix);
export default Eng;
Currently, only services and route paths (see below) can be shared across the parent/engine boundary.
Linking to routes outside of an Engine's isolated context is currently supported by defining "external routes" as dependencies of your Engine.
You specify what external things your Engine wants to link to by providing an array of names like so:
// ember-blog/addon/engine.js
export default Engine.extend({
// ...
dependencies: {
externalRoutes: [
'home',
'settings'
]
}
});
The Engine's consumer is then responsible for defining where those things are located via a route path:
// dummy/app/app.js
const App = Ember.Application.extend({
modulePrefix,
podModulePrefix,
Resolver,
engines: {
emberBlog: {
dependencies: {
externalRoutes: {
home: 'home.index',
settings: 'settings.blog.index'
}
}
}
}
});
You can then use those external routes either programmatically or within a template like so:
// ember-blog/addon/some-route.js
this.transitionToExternal('settings');
For further documentation on this subject, view the Engine Linking RFC.
As in an application, you can provide configuration settings for your
engine in config/environment.js
. You can access these settings in a
couple different ways.
The simplest method is to import these settings:
// addon/engine.js
import config from './config/environment';
console.log(config.modulePrefix);
Configuration settings are also registered with the key config:environment
and
can be looked up given an engine instance. For example:
// addon/instance-initializers/hello-instance.js
export function initialize(engineInstance) {
let config = engineInstance.resolveRegistration('config:environment');
console.log('modulePrefix', config.modulePrefix);
}
export default {
name: 'hello-instance',
initialize: initialize
};
Engines that are published as separate addons should be installed like any other addon:
ember install <engine-name>
As mentioned above, engines can also exist as in-repo addons, in which case
you just need to ensure that this addon (ember-engines
) has been installed
in your main project.
An Application or Engine that contains other engines must use the Resolver
provided in the ember-engines/resolver
module. For example:
import Ember from 'ember';
import Resolver from 'ember-engines/resolver'; // <=== IMPORTANT - custom resolver!!!
import loadInitializers from 'ember/load-initializers';
import config from './config/environment';
Ember.MODEL_FACTORY_INJECTIONS = true;
const { modulePrefix, podModulePrefix } = config;
const App = Ember.Application.extend({
modulePrefix,
podModulePrefix,
Resolver
});
loadInitializers(App, modulePrefix);
export default App;
Route-less engines can be rendered in a template using the {{mount}}
keyword.
Route-less engines can be mounted in templates using the {{mount}}
keyword.
For example, the following template renders the ember-chat
engine:
Currently, the engine name is the only argument that can be passed to
{{mount}}
.
Routable engines should be mounted in your router's route map using the
mount()
method. For example:
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('blogs', function() {
// Mount the main blog at /blogs/ember-blog
this.mount('ember-blog');
// Mount the hr blog at /blogs/hr-blog
this.mount('ember-blog', { as: 'hr-blog' });
// Mount the admin blog at /blogs/special-admin-blog-here
this.mount('ember-blog', { as: 'admin-blog', path: '/special-admin-blog-here' });
});
});
export default Router;
The above example mounts three different instances of the ember-blog
engine
within the blogs
route.
The engine mounted with this.mount('ember-blog')
will have a root path of
/blogs/ember-blog
and its root route can be referenced as ember-blog
.
The engine mounted with this.mount('ember-blog', { as: 'hr-blog' })
will have
a root path of /blogs/hr-blog
and its root route can be referenced as
hr-blog
.
The engine mounted with this.mount('ember-blog', { as: 'admin-blog', path: '/special-admin-blog-here' })
will have a root path of
/blogs/special-admin-blog-here
and its root route can be referenced as
admin-blog
.
Note: The above example is not very practical currently without a method to
configure individual instances of ember-blog
.
Applications or engines that contain an engine must provide mappings that fulfill the dependencies required by that engine.
For example, the following engine expects its parent to provide store
and
session
services:
import Engine from 'ember-engines/engine';
import Resolver from 'ember-engines/resolver';
export default Engine.extend({
modulePrefix: 'ember-blog',
Resolver,
dependencies: {
services: [
'store',
'session'
]
}
});
An application that contains this engine must explicitly fulfill these dependencies. For example:
import Ember from 'ember';
import Resolver from 'ember-engines/resolver';
import loadInitializers from 'ember/load-initializers';
import config from './config/environment';
Ember.MODEL_FACTORY_INJECTIONS = true;
const { modulePrefix, podModulePrefix } = config;
const App = Ember.Application.extend({
modulePrefix,
podModulePrefix,
Resolver,
engines: {
emberBlog: {
dependencies: {
services: [
'store',
{'session': 'user-session'}
]
}
}
}
});
loadInitializers(App, modulePrefix);
export default App;
Note that the app's store
service is directly mapped to the engine's store
service, while the app's user-session
service is mapped to the engine's
session
service.
Also note that multiple engines can be configured per parent application/engine,
and that each engine name should be camelCased (emberBlog
instead of
ember-blog
).
git clone
this repositorynpm install
bower install
ember server
- Visit your app at http://localhost:4200.
npm test
(Runsember try:testall
to test your addon against multiple Ember versions)ember test
ember test --server
ember build
For more information on using ember-cli, visit http://www.ember-cli.com/.
Copyright 2015-2016 Dan Gebhardt and Robert Jackson. MIT License (see LICENSE.md for details).