-
-
Notifications
You must be signed in to change notification settings - Fork 407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Engines #10
RFC: Engines #10
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
- Start Date: 2014-10-24 | ||
- RFC PR: | ||
- Ember Issue: | ||
|
||
# Summary | ||
|
||
Engines allow multiple logical applications to be composed together into | ||
a single application from the user's perspective. They also allow addons | ||
to provide encapsulated functionality normally associated with apps, | ||
such as routes and templates that integrate seamlessly with the primary | ||
app. | ||
|
||
# Motivation | ||
|
||
Large companies are increasingly adopting Ember.js to power their entire | ||
product lines. Often, this means separate teams (sometimes distributed | ||
around the world) working on the same app. Typically, responsibility is | ||
shared by dividing the application into one or more "sections". How this | ||
division is actually implemented varies from team to team. | ||
|
||
Sometimes, each "section" will be a completely separate Ember app, with | ||
a shared navigation bar allowing users to switch between each app. This | ||
allows teams to work quickly without stepping on each others' toes, but | ||
switching apps feels slow (especially compared to the normally speedy | ||
route transitions in Ember) because the entire page must be thrown out, | ||
then an entirely new set of the same assets downloaded and parsed. | ||
Additionally, code sharing is largely accomplished via copy-and-paste. | ||
|
||
Other times, the separation is enforced socially, with each team | ||
claiming a section of the same app in the same repository. | ||
Unfortunately, this approach leads to frequent conflicts around shared | ||
resources, and feedback from tests gets slower and slower as test suites | ||
grow in size. | ||
|
||
Engines allow each section of an application to be developed in | ||
isolation, including separate test suites and naming conventions. These | ||
isolated units can be composed together into a single app from the | ||
perspective of the user. | ||
|
||
Additionally, engines allow addons to augment applications with | ||
off-the-shelf fully-functional UI. For example, imagine an | ||
authentication library that offers a prefab login screen that | ||
application developers can opt in to, dramatically increasing the speed | ||
at which prototypes can be developed. | ||
|
||
Or imagine a product like Discourse allowing you to mount their Ember | ||
app at `/forums/`, so the user needn't leave your app to read your | ||
discussion forum. | ||
|
||
# Detailed Design | ||
|
||
Your app can gain access to an engine in one of two ways: | ||
|
||
1. By placing an Ember CLI app inside another Ember CLI app's `engines/` | ||
directory (a sibling of `app/`). | ||
2. By installing an Ember CLI addon that contains an `app` directory. | ||
|
||
Engines are not loaded until the primary application mounts them | ||
explicitly. The primary application uses the new `mount()` router DSL | ||
method to mount an engine: | ||
|
||
```js | ||
Router.map(function() { | ||
this.mount('an-engine'); | ||
}); | ||
``` | ||
|
||
Engines' routes are prefixed with the name of the engine. For example, | ||
let's write an engine that adds authentication, and includes a default | ||
login and logout page. Imaginatively, we'll call this engine | ||
`authentication`. | ||
|
||
```js | ||
// engines/authentication/router.js | ||
|
||
import Ember from 'ember'; | ||
|
||
var Router = Ember.Router.extend(); | ||
|
||
Router.map(function() { | ||
this.resource('login'); | ||
this.resource('logout'); | ||
}); | ||
|
||
export default Router; | ||
``` | ||
|
||
Now, in the primary app, we mount the `authentication` engine: | ||
|
||
```js | ||
// app/router.js | ||
|
||
import Ember from 'ember'; | ||
|
||
var Router = Ember.Router.extend(); | ||
|
||
Router.map(function() { | ||
this.mount('authentication'); | ||
}); | ||
|
||
export default Router; | ||
``` | ||
|
||
Now the primary app gains two new URLs: | ||
|
||
1. `/authentication/login` | ||
2. `/authentication/logout` | ||
|
||
If I want to customize the prefix used, I can pass options to `mount()` | ||
that specify a path: | ||
|
||
```js | ||
this.mount('authentication', { path: 'auth' }); | ||
``` | ||
|
||
Now the URLs become: | ||
|
||
1. `/auth/login` | ||
2. `/auth/logout` | ||
|
||
Engines otherwise behave like addons. Thanks to [the PR adding | ||
namespaces to the Ember CLI resolver][namespaces], container keys can | ||
specify an engine. For example, if the `authentication` engine contains | ||
`engines/authentication/models/user.js`, this could be resolved via the | ||
container with: | ||
|
||
```js | ||
container.lookup('authentication@model:user'); | ||
``` | ||
|
||
[namespaces]: https://github.com/stefanpenner/ember-resolver/pull/70 | ||
|
||
Other APIs in Ember would need to be extended to support namespaces to | ||
take full advantage of this feature. For example, components that ship | ||
with an engine could be accessed from the primary application like this: | ||
|
||
```handlebars | ||
{{authentication@login-form obscure-password=true}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. downside of this syntax is that it has no html compliant tag form (yet) In theory something like: <ember-namespace namespace="authentication">
<login-form>
</ember-namespace> or a block form, may introduce nice things There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stefanpenner How's that HTML-compliant tags align with (presumably) allowing tags with slashes after the Handlebars 2.0 update? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for now we will support slashes only via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually seems like a somewhat serious issue. We're relying on HTMLBars syntax for something like: We will need to figure out a solution for nested tag names pretty soon. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: I think our solutions are going to end up being:
I think that many solutions will work, and that as long as we have a clear, "one true story" for doing this, and it doesn't introduce excessive duplication or boilerplate, people will be able to live with it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its a bummer that |
||
``` | ||
|
||
However, these changes are outside the scope of this RFC. | ||
|
||
## Generator | ||
|
||
Create a new engine by running `ember g engine-name`. | ||
|
||
# Drawbacks | ||
|
||
This RFC introduces the new concept of engines, which increases the | ||
learning curve of the framework. However, I believe this issue is | ||
mitigated by the fact that engines are an opt-in packaging around | ||
existing concepts. | ||
|
||
In the end, I believe that "engines" are just a small API for composing | ||
existing concepts. And they can be introduced at the top of the | ||
conceptual ladder, once users are comfortable with the basics of Ember, | ||
and only for those working on large teams or distributing addons. | ||
|
||
# Alternatives | ||
|
||
One alternative is to simply allow addons to merge app-like assets | ||
(templates, routes, etc.) with the primary app (e.g. via Broccoli's | ||
`mergeTrees`). However, this is highly likely to lead to naming | ||
collisions. By providing an isolated app that users can mount at a path | ||
of their choosing, we can ensure that collisions are next to impossible. | ||
|
||
# Unresolved questions | ||
|
||
## Non-CLI Users | ||
|
||
This RFC assumes Ember CLI. I would prefer to prove this out in Ember | ||
CLI before locking down the public APIs/hooks the router exposes for | ||
locating and mounting engines. Once this is done, however, we should | ||
expose and document those hooks so users who cannot use Ember CLI for | ||
whatever reason can still take advantage of composability. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should be sure it de-sugars to something non CLI. In theory we can make engines able to pre-build themselves (with cli) for consumption without CLI. This is also interesting and appealing, as engines being able to pre-build themselves will allow for much faster build times. (Something @chadhietala's work on CLI aims to support) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree on all counts 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would love this! It would also allow deploying engines separately, and then adding them dynamically to the page as plugins, so not increasing bloat for those users that don't have the plugin enabled. |
||
|
||
## Dependencies (Shared vs. Isolated) | ||
|
||
Because each engine is an isolated app, it will have its own set of | ||
dependencies. Frequently, those dependencies will be the same as the | ||
dependencies of the primary app. For example, it would be bad to ship | ||
multiple copies of Ember, one for each engine. Or, imagine both the app | ||
and an engine required a Bootstrap add-on that included CSS. It would | ||
not do to include multiple (perhaps subtly different) copies of the same | ||
CSS. | ||
|
||
Ideally, dependencies would be deduplicated using a strategy described | ||
by @wycats in [this Google doc](https://docs.google.com/a/tomdale.net/document/d/12CsR-zli5oP2TDWOef_-D28zjmbVD83hU4q9_VTk-9s/edit). | ||
|
||
## App/Container/File Layout | ||
|
||
Does each engine have its own container? Or does it register factories | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. point of clarification. in ember-cli there is no concept of registering, merely resolving on-demand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also feel the layout should mostly mimic a normal ember-cli app's app directory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, absolutely. In Rails, the primary difference is that Applications have the notion of booting, and certain app-global configuration. The TLDR is: Application extends Engine extends Plugin. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, i would like our main app (and our addons) to mimic this same. Engines all the way down. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stefanpenner and I have talked about this in part. Currently in ember-cli apps and addons have two different compilation pipelines. Part of this effort might include moving ember-cli to consider the application itself as an addon/engine. This would consolidate the compilation pipelines and would demand that the build system revolve around engines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bcardarella yupyup :) |
||
with the host app's container? | ||
|
||
Do engines subclass `Ember.Application`, or do we introduce something | ||
like `Ember.Engine`? | ||
|
||
How do engines use initializers? Can they inject into their own | ||
namespace, or into the primary app also? How do you disambiguate? | ||
|
||
Where does configuration go? Do engines put all of their app code in | ||
`app/` like full-blown apps? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to consider loading engines at runtime? I know some apps do something like
/admin/*
toroute:load_admin
statically in the core approute:load_admin
fetches the JS, CSS, and (compiled) templates for the Admin Engineroute:load_admin
defines new routes foradmin/x
,admin/y
, etc. Because they're more specific thanadmin/*
, they have higher routing priority.route:load_admin
restarts the transition.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using that strategy a lot in Ember 1.10. It seems like it broke in Ember 1.11 (I've not been able to confirm if there's another workaround). In order for step 3 to work, we were calling Router.map a second time, however, when doing that in Ember 1.11 the new routes weren't recognized :(
I know there was a big refactor of the router in 1.11, so maybe this broke after that.