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

Merged
merged 4 commits into from Apr 11, 2016

Conversation

Projects
None yet
@tomdale
Member

tomdale commented Oct 25, 2014

RFC

Show outdated Hide outdated active/0000-engines.md
with an engine could be accessed from the primary application like this:
```handlebars
{{authentication@login-form obscure-password=true}}

This comment has been minimized.

@stefanpenner

stefanpenner Oct 25, 2014

Member

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

@stefanpenner

stefanpenner Oct 25, 2014

Member

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

This comment has been minimized.

@zeppelin

zeppelin Oct 28, 2014

@stefanpenner How's that HTML-compliant tags align with (presumably) allowing tags with slashes after the Handlebars 2.0 update?

@zeppelin

zeppelin Oct 28, 2014

@stefanpenner How's that HTML-compliant tags align with (presumably) allowing tags with slashes after the Handlebars 2.0 update?

This comment has been minimized.

@stefanpenner

stefanpenner Oct 28, 2014

Member

for now we will support slashes only via {{foo/bar}} syntax, as <foo/bar></foo/bar> is kinda crazy and not supported by HTMLBars currently. I am unsure it should be, but likely further thought is needed.

@stefanpenner

stefanpenner Oct 28, 2014

Member

for now we will support slashes only via {{foo/bar}} syntax, as <foo/bar></foo/bar> is kinda crazy and not supported by HTMLBars currently. I am unsure it should be, but likely further thought is needed.

This comment has been minimized.

@wycats

wycats Oct 28, 2014

Member

This actually seems like a somewhat serious issue. We're relying on HTMLBars syntax for something like: <my-component on-click={{action "foo"}}>, and there is no equivalent old-component syntax.

We will need to figure out a solution for nested tag names pretty soon.

@wycats

wycats Oct 28, 2014

Member

This actually seems like a somewhat serious issue. We're relying on HTMLBars syntax for something like: <my-component on-click={{action "foo"}}>, and there is no equivalent old-component syntax.

We will need to figure out a solution for nested tag names pretty soon.

This comment has been minimized.

@wycats

wycats Oct 28, 2014

Member

FYI: document.createElement("foo:bar") works (holdover from XML days, I suppose).

I think our solutions are going to end up being:

  • Template-wide imports (* or named)
  • Scoped imports (like XML namespaces or what Stef suggested)
  • Changes to the default namespace (would require html:div if HTML was desired).

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.

@wycats

wycats Oct 28, 2014

Member

FYI: document.createElement("foo:bar") works (holdover from XML days, I suppose).

I think our solutions are going to end up being:

  • Template-wide imports (* or named)
  • Scoped imports (like XML namespaces or what Stef suggested)
  • Changes to the default namespace (would require html:div if HTML was desired).

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.

This comment has been minimized.

@stefanpenner

stefanpenner Oct 29, 2014

Member

its a bummer that @ wont work, as : already has meaning...

@stefanpenner

stefanpenner Oct 29, 2014

Member

its a bummer that @ wont work, as : already has meaning...

Show outdated Hide outdated active/0000-engines.md
## App/Container/File Layout
Does each engine have its own container? Or does it register factories

This comment has been minimized.

@stefanpenner

stefanpenner Oct 25, 2014

Member

point of clarification. in ember-cli there is no concept of registering, merely resolving on-demand.

@stefanpenner

stefanpenner Oct 25, 2014

Member

point of clarification. in ember-cli there is no concept of registering, merely resolving on-demand.

This comment has been minimized.

@stefanpenner

stefanpenner Oct 25, 2014

Member

I also feel the layout should mostly mimic a normal ember-cli app's app directory.

@stefanpenner

stefanpenner Oct 25, 2014

Member

I also feel the layout should mostly mimic a normal ember-cli app's app directory.

This comment has been minimized.

@wycats

wycats Oct 28, 2014

Member

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.

@wycats

wycats Oct 28, 2014

Member

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.

This comment has been minimized.

@stefanpenner

stefanpenner Oct 29, 2014

Member

yes, i would like our main app (and our addons) to mimic this same. Engines all the way down.

@stefanpenner

stefanpenner Oct 29, 2014

Member

yes, i would like our main app (and our addons) to mimic this same. Engines all the way down.

This comment has been minimized.

@bcardarella

bcardarella Oct 29, 2014

Contributor

@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.

@bcardarella

bcardarella Oct 29, 2014

Contributor

@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.

This comment has been minimized.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 25, 2014

Member

Elephant in the room is async. I believe engines can be a great enabler of this, but some concerns exist.

  • routing to to routes not-yet-loaded
  • link-to to routes not loaded
  • link-to to routes not loaded (where the routes have custom serialize)
  • foot-gun reopen + reopenClass invocations on classes who already have instantiated instances

Implicit vs Explicit namespaces within an engine.

Another, topic of discussion is, does one need to constantly use the namespace within the namespace, for example. Within authentication does one need to use:

this.controllerFor('authentication@foo');

or can we without ambiguity consistently infer the namespace and only do.

this.controllerFor('foo');

Engines all the way down.

In a perfect world, top level applications would also be engines and this would in "pure" style of engines all the way down. Does this make sense, is this tenable etc..

Member

stefanpenner commented Oct 25, 2014

Elephant in the room is async. I believe engines can be a great enabler of this, but some concerns exist.

  • routing to to routes not-yet-loaded
  • link-to to routes not loaded
  • link-to to routes not loaded (where the routes have custom serialize)
  • foot-gun reopen + reopenClass invocations on classes who already have instantiated instances

Implicit vs Explicit namespaces within an engine.

Another, topic of discussion is, does one need to constantly use the namespace within the namespace, for example. Within authentication does one need to use:

this.controllerFor('authentication@foo');

or can we without ambiguity consistently infer the namespace and only do.

this.controllerFor('foo');

Engines all the way down.

In a perfect world, top level applications would also be engines and this would in "pure" style of engines all the way down. Does this make sense, is this tenable etc..

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Oct 25, 2014

Member

@stefanpenner Container keys used in the context of an engine should implicitly be in the same namespace; i.e. within authentication one can just say this.controllerFor('foo'); to get the authentication engine's foo controller.

Member

tomdale commented Oct 25, 2014

@stefanpenner Container keys used in the context of an engine should implicitly be in the same namespace; i.e. within authentication one can just say this.controllerFor('foo'); to get the authentication engine's foo controller.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 25, 2014

Member

@stefanpenner Container keys used in the context of an engine should implicitly be in the same namespace; i.e. within authentication one can just say this.controllerFor('foo'); to get the authentication engine's foo controller.

so this would imply a custom container (or namespace aware shared container). What about the resolver, does each engine use the same resolver?

I would love to work through some gnarly multi-container multi engine examples as I believe this will be more complicated then we will expect. Some early ideas @rwjblue and me through down https://github.com/rwjblue/container-resolver-namespaces

Member

stefanpenner commented Oct 25, 2014

@stefanpenner Container keys used in the context of an engine should implicitly be in the same namespace; i.e. within authentication one can just say this.controllerFor('foo'); to get the authentication engine's foo controller.

so this would imply a custom container (or namespace aware shared container). What about the resolver, does each engine use the same resolver?

I would love to work through some gnarly multi-container multi engine examples as I believe this will be more complicated then we will expect. Some early ideas @rwjblue and me through down https://github.com/rwjblue/container-resolver-namespaces

@lukemelia

This comment has been minimized.

Show comment
Hide comment
@lukemelia

lukemelia Oct 25, 2014

Member

I love where this is going, and I think it will be very helpful lots of teams. I think this RFC should include the mechanism(s) that would be available to for the application developer to modify classes and templates provided by the engine.

e.g. in the authentication scenario you described, there is an out-of-the-box login.hbs template. How would a developer provide her own template to be used instead? Same question for the login controller. And would this be on an engine-by-engine basis, or would there be an automatically supported convention for this?

Member

lukemelia commented Oct 25, 2014

I love where this is going, and I think it will be very helpful lots of teams. I think this RFC should include the mechanism(s) that would be available to for the application developer to modify classes and templates provided by the engine.

e.g. in the authentication scenario you described, there is an out-of-the-box login.hbs template. How would a developer provide her own template to be used instead? Same question for the login controller. And would this be on an engine-by-engine basis, or would there be an automatically supported convention for this?

@jasonmit

This comment has been minimized.

Show comment
Hide comment

👍

This comment has been minimized.

Show comment
Hide comment

+1

This comment has been minimized.

Show comment
Hide comment

👍

This comment has been minimized.

Show comment
Hide comment
@davidlormor

davidlormor Nov 13, 2014

As someone doing multiple Ember apps on a single Rails platform...I'm definitely interested to see this take shape! Big +1

As someone doing multiple Ember apps on a single Rails platform...I'm definitely interested to see this take shape! Big +1

This comment has been minimized.

Show comment
Hide comment
@kriswill

kriswill Feb 13, 2015

Making Ember.Engine the ancestor to Ember.Application could resolve some of the issues around composability. Where Ember.Application is simply a bootable engine. Then use config/environment.js to configure namespace aliases (to resolve collisions) and declare shared resources, or perhaps make the build pipeline skip Less or SCSS compilation for an app-local alternative, which could bring in those resources and incorporate them into the local pipeline.

Making Ember.Engine the ancestor to Ember.Application could resolve some of the issues around composability. Where Ember.Application is simply a bootable engine. Then use config/environment.js to configure namespace aliases (to resolve collisions) and declare shared resources, or perhaps make the build pipeline skip Less or SCSS compilation for an app-local alternative, which could bring in those resources and incorporate them into the local pipeline.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Oct 25, 2014

Member

@stefanpenner

so this would imply a custom container (or namespace aware shared container)

My gut feeling here is that you'd want some kind of container per engine, but the container for engines would just be a sort of proxy that ensured everything on the way out was namespaced.

So basically, if a controller inside an engine did this.controllerFor('foo'):

  1. controllerFor('foo') would do this.container.lookup('controller:foo')
  2. The engine's container would know its namespace, authentication, and proxy a call to the "parent" container with authentication@controller:foo (parent nomenclature is a little confusing here)
Member

tomdale commented Oct 25, 2014

@stefanpenner

so this would imply a custom container (or namespace aware shared container)

My gut feeling here is that you'd want some kind of container per engine, but the container for engines would just be a sort of proxy that ensured everything on the way out was namespaced.

So basically, if a controller inside an engine did this.controllerFor('foo'):

  1. controllerFor('foo') would do this.container.lookup('controller:foo')
  2. The engine's container would know its namespace, authentication, and proxy a call to the "parent" container with authentication@controller:foo (parent nomenclature is a little confusing here)
@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Oct 25, 2014

Member

@lukemelia

e.g. in the authentication scenario you described, there is an out-of-the-box login.hbs template. How would a developer provide her own template to be used instead? Same question for the login controller. And would this be on an engine-by-engine basis, or would there be an automatically supported convention for this?

That seems like it should be up to the engine author to me. In the specific case of authentication, I can imagine the author building the login page out of a series of components that talk to a service. If you don't like the built-in login page, you can create your own page and re-configure the provided components. Or, if you want totally custom UI, you can just inject the service and talk to it directly from custom components.

Member

tomdale commented Oct 25, 2014

@lukemelia

e.g. in the authentication scenario you described, there is an out-of-the-box login.hbs template. How would a developer provide her own template to be used instead? Same question for the login controller. And would this be on an engine-by-engine basis, or would there be an automatically supported convention for this?

That seems like it should be up to the engine author to me. In the specific case of authentication, I can imagine the author building the login page out of a series of components that talk to a service. If you don't like the built-in login page, you can create your own page and re-configure the provided components. Or, if you want totally custom UI, you can just inject the service and talk to it directly from custom components.

@lukemelia

This comment has been minimized.

Show comment
Hide comment
@lukemelia

lukemelia Oct 25, 2014

Member

@tomdale wrote

That seems like it should be up to the engine author to me. In the specific case of authentication, I can imagine the author building the login page out of a series of components that talk to a service. If you don't like the built-in login page, you can create your own page and re-configure the provided components. Or, if you want totally custom UI, you can just inject the service and talk to it directly from custom components.

In this scenario, how would I tell the engine's login route to use the page that I crafted out of the engine's components? Does needing to change a small detail on the login screen mean I need to replace the template, route and router?

It would be awesome (though potentially a footgun) if there were a way to make it easy for engine authors to either explicitly provide extension points, or to have every piece of an engine overridable by a) providing a file at a corresponding path in your own app, e.g. under ext/engines/engine-name and/or b) registering your own implementation with the container.

Member

lukemelia commented Oct 25, 2014

@tomdale wrote

That seems like it should be up to the engine author to me. In the specific case of authentication, I can imagine the author building the login page out of a series of components that talk to a service. If you don't like the built-in login page, you can create your own page and re-configure the provided components. Or, if you want totally custom UI, you can just inject the service and talk to it directly from custom components.

In this scenario, how would I tell the engine's login route to use the page that I crafted out of the engine's components? Does needing to change a small detail on the login screen mean I need to replace the template, route and router?

It would be awesome (though potentially a footgun) if there were a way to make it easy for engine authors to either explicitly provide extension points, or to have every piece of an engine overridable by a) providing a file at a corresponding path in your own app, e.g. under ext/engines/engine-name and/or b) registering your own implementation with the container.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 25, 2014

Member

@lukemelia it would seem like adding custom modules to engine/engine-name/templates/login.hbs would do the trick. As a 3rd party engine would actually exists in node_modules somewhere, so as long as engine/<name>/templates/login.hbs has a higher priority then node_modules/auth-plugin/engine/template/login.hbs when merging, it may "just work"

Member

stefanpenner commented Oct 25, 2014

@lukemelia it would seem like adding custom modules to engine/engine-name/templates/login.hbs would do the trick. As a 3rd party engine would actually exists in node_modules somewhere, so as long as engine/<name>/templates/login.hbs has a higher priority then node_modules/auth-plugin/engine/template/login.hbs when merging, it may "just work"

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 25, 2014

Member

My gut feeling here is that you'd want some kind of container per engine, but the container for engines would just be a sort of proxy that ensured everything on the way out was namespaced.

ya, container + resolver pair per engine, with a global registry to enable escaping the current namespace.

Member

stefanpenner commented Oct 25, 2014

My gut feeling here is that you'd want some kind of container per engine, but the container for engines would just be a sort of proxy that ensured everything on the way out was namespaced.

ya, container + resolver pair per engine, with a global registry to enable escaping the current namespace.

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Oct 25, 2014

Contributor

There may be good reasons to not pave an easy path for engines to escape their own namespace. What if we constrained their external interface to only be:

  • a set of routes they expose, which the rest of the application can transitionTo, link-to, etc.
  • a set of events that can bubble out of the engine up to the parent route of its mount point, just like normal route bubbling.

From the outside looking in, no new namespacing interface is needed because the user already namespaced these routes based on where they called mount and the value of the proposed path argument. Within the engine, every route would just be relative to the mount point.

The engine would not directly link-to or transitionTo routes outside its own namespace. It can get all the same effects by bubbling events out and letting the containing application (or the next engine up the stack!) deal with it. The engine couldn't do controllerFor, modelFor, etc outside its own scope, which I suspect is a good thing.

This is fairly analogous to the way components work. In this sense, engines are just super-components that happen to handle their own routing.

Contributor

ef4 commented Oct 25, 2014

There may be good reasons to not pave an easy path for engines to escape their own namespace. What if we constrained their external interface to only be:

  • a set of routes they expose, which the rest of the application can transitionTo, link-to, etc.
  • a set of events that can bubble out of the engine up to the parent route of its mount point, just like normal route bubbling.

From the outside looking in, no new namespacing interface is needed because the user already namespaced these routes based on where they called mount and the value of the proposed path argument. Within the engine, every route would just be relative to the mount point.

The engine would not directly link-to or transitionTo routes outside its own namespace. It can get all the same effects by bubbling events out and letting the containing application (or the next engine up the stack!) deal with it. The engine couldn't do controllerFor, modelFor, etc outside its own scope, which I suspect is a good thing.

This is fairly analogous to the way components work. In this sense, engines are just super-components that happen to handle their own routing.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Oct 25, 2014

Member

a set of events that can bubble out of the engine up to the parent route of its mount point, just like normal route bubbling.

As a heads up, we're considering moving away from a "bubbling events" model to one where consumers of components pass in callbacks that can be called (sort of like React but without the verbosity).

Member

tomdale commented Oct 25, 2014

a set of events that can bubble out of the engine up to the parent route of its mount point, just like normal route bubbling.

As a heads up, we're considering moving away from a "bubbling events" model to one where consumers of components pass in callbacks that can be called (sort of like React but without the verbosity).

@juggy

This comment has been minimized.

Show comment
Hide comment
@juggy

juggy Oct 25, 2014

I currently have an app that is broke down into multiple rails engine. Each rails engine has its own Ember namespace with routes, models, templates, etc. Here is for example the initializer for a CRM engine:

Crm = Ember.Namespace.create()

Ember.Application.initializer
  name: 'router:crm',

  initialize: (container, application)->
    router = container.lookup("router:main")

    # Load the CRM route classes
    for klassName, klass of Crm

      if klassName.indexOf("Route") > 0
        klassName = klassName.replace("Route", "")
        klassName = "route:" + klassName.dasherize().replace(/\-/g, ".")
      else if klassName.indexOf("Controller") > 0
        klassName = klassName.replace("Controller", "")
        klassName = "controller:" + klassName.dasherize().replace(/\-/g, ".")
      else
        continue

      application.register( klassName, klass )  

    # Load the paths into the router
    router.constructor.map ->
      @resource 'parties', ->
        @route('new')
        @route('edit', {path : '/:id/edit'})
        @resource 'parties.show', {path : '/:id'}, ->
          @route('details', {path : '/details'})
          @route('activities', {path : '/activities'})

The first part of the initializer takes all the controllers/routes from the crm namespace and register them with the main application. The second part, well, is the router definition for the engine.

The drawback is that the main application currently does not know about the other namespaces and does not decide which routes get mounted.

Now the fun part is when you want to add routes to a 'dependent' engine. Let's say we have a project engine dependent on the crm engine. We would like to enhance a contact with a list of projects. The means:

  • change the model to add a relationship to projects
  • change the routes to handle /contacts/:id/projects
  • change the template to include the link to the new projects route

Here is the code for the project namespace initializer:

Projects = Ember.Namespace.create()
Ember.Application.initializer
  name: 'router:projects',

  initialize: (container, application)->
    router = container.lookup("router:main")

    # Load the Projects route classes
    for klassName, klass of Projects

      if klassName.indexOf("Route") > 0
        klassName = klassName.replace("Route", "")
        klassName = "route:" + klassName.dasherize().replace(/\-/g, ".")
      else if klassName.indexOf("Controller") > 0
        klassName = klassName.replace("Controller", "")
        klassName = "controller:" + klassName.dasherize().replace(/\-/g, ".")
      else
        continue

      application.register( klassName, klass )  

    # Load the paths into the router
    router.constructor.map ->
       @resource 'parties', ->
         @resource 'parties.show', {path:"/:id"}, ->
           @route("projects", {path: "/projects"})

      @resource 'projects', ->
        @route('new')
        @route('edit', {path : '/:id/edit'})
        @resource 'projects.show', {path : '/:id'}, ->
          ...

The first part is the same as for the Crm engine can could be extracted for reuse. The second part loads the projects routes, but also add a new projects route in the parties route.

For the template, I create a new template with the exact same path and name that will be loaded after the crm template. It is a copy of the original template so it is not optimal, but there is no easy way of doing it otherwise. For what it is worth, rails uses the same "copy to extend" pattern for the views in engines.

As for the model, I just reopen the class within the project namespace and add the relationship. Same goes for new actions on the controllers, routes, etc.

The technique above works really well for now and if you can build upon that, that would be sweet. I hope this helps!

juggy commented Oct 25, 2014

I currently have an app that is broke down into multiple rails engine. Each rails engine has its own Ember namespace with routes, models, templates, etc. Here is for example the initializer for a CRM engine:

Crm = Ember.Namespace.create()

Ember.Application.initializer
  name: 'router:crm',

  initialize: (container, application)->
    router = container.lookup("router:main")

    # Load the CRM route classes
    for klassName, klass of Crm

      if klassName.indexOf("Route") > 0
        klassName = klassName.replace("Route", "")
        klassName = "route:" + klassName.dasherize().replace(/\-/g, ".")
      else if klassName.indexOf("Controller") > 0
        klassName = klassName.replace("Controller", "")
        klassName = "controller:" + klassName.dasherize().replace(/\-/g, ".")
      else
        continue

      application.register( klassName, klass )  

    # Load the paths into the router
    router.constructor.map ->
      @resource 'parties', ->
        @route('new')
        @route('edit', {path : '/:id/edit'})
        @resource 'parties.show', {path : '/:id'}, ->
          @route('details', {path : '/details'})
          @route('activities', {path : '/activities'})

The first part of the initializer takes all the controllers/routes from the crm namespace and register them with the main application. The second part, well, is the router definition for the engine.

The drawback is that the main application currently does not know about the other namespaces and does not decide which routes get mounted.

Now the fun part is when you want to add routes to a 'dependent' engine. Let's say we have a project engine dependent on the crm engine. We would like to enhance a contact with a list of projects. The means:

  • change the model to add a relationship to projects
  • change the routes to handle /contacts/:id/projects
  • change the template to include the link to the new projects route

Here is the code for the project namespace initializer:

Projects = Ember.Namespace.create()
Ember.Application.initializer
  name: 'router:projects',

  initialize: (container, application)->
    router = container.lookup("router:main")

    # Load the Projects route classes
    for klassName, klass of Projects

      if klassName.indexOf("Route") > 0
        klassName = klassName.replace("Route", "")
        klassName = "route:" + klassName.dasherize().replace(/\-/g, ".")
      else if klassName.indexOf("Controller") > 0
        klassName = klassName.replace("Controller", "")
        klassName = "controller:" + klassName.dasherize().replace(/\-/g, ".")
      else
        continue

      application.register( klassName, klass )  

    # Load the paths into the router
    router.constructor.map ->
       @resource 'parties', ->
         @resource 'parties.show', {path:"/:id"}, ->
           @route("projects", {path: "/projects"})

      @resource 'projects', ->
        @route('new')
        @route('edit', {path : '/:id/edit'})
        @resource 'projects.show', {path : '/:id'}, ->
          ...

The first part is the same as for the Crm engine can could be extracted for reuse. The second part loads the projects routes, but also add a new projects route in the parties route.

For the template, I create a new template with the exact same path and name that will be loaded after the crm template. It is a copy of the original template so it is not optimal, but there is no easy way of doing it otherwise. For what it is worth, rails uses the same "copy to extend" pattern for the views in engines.

As for the model, I just reopen the class within the project namespace and add the relationship. Same goes for new actions on the controllers, routes, etc.

The technique above works really well for now and if you can build upon that, that would be sweet. I hope this helps!

@bcardarella

This comment has been minimized.

Show comment
Hide comment
@bcardarella

bcardarella Oct 26, 2014

Contributor

I'm confused between the difference of this RFC and ember-cli's already existing addons. Addons are effectively rails-like engines already. Beyond the engine-specific container I see a lot of overlap.

Contributor

bcardarella commented Oct 26, 2014

I'm confused between the difference of this RFC and ember-cli's already existing addons. Addons are effectively rails-like engines already. Beyond the engine-specific container I see a lot of overlap.

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Oct 26, 2014

Contributor

@bcardarella I mostly agree. The only thing addons can't do is add routes to the router map. Even that could be done today by exporting a mount function from the add-on that the user can call from their router map.

Contributor

ef4 commented Oct 26, 2014

@bcardarella I mostly agree. The only thing addons can't do is add routes to the router map. Even that could be done today by exporting a mount function from the add-on that the user can call from their router map.

@bcardarella

This comment has been minimized.

Show comment
Hide comment
@bcardarella

bcardarella Oct 26, 2014

Contributor

@ef4 yup, ember-admin is kind of doing this already but you have to opt-into it: https://github.com/dockyard/ember-admin#usage

@rwjblue and I have discussed a few time renaming the addons to engines, and I think the scope of ability of addons have outgrown the term "addon" itself. I'm all for this RFC I just don't think there needs to be a separate effort.

Contributor

bcardarella commented Oct 26, 2014

@ef4 yup, ember-admin is kind of doing this already but you have to opt-into it: https://github.com/dockyard/ember-admin#usage

@rwjblue and I have discussed a few time renaming the addons to engines, and I think the scope of ability of addons have outgrown the term "addon" itself. I'm all for this RFC I just don't think there needs to be a separate effort.

@drogus

This comment has been minimized.

Show comment
Hide comment
@drogus

drogus Oct 26, 2014

One thing that is not discussed in the proposal is handling links between an engine and an application. In the example of authentication, it's almost certain that you will need to link to login page of the engine from the host application, for example {{link-to "auth.login"}}. I'm not sure what should be the syntax and how the engine should be referred. In Rails there's an option to name an engine using :as option, like mount Authentication::Engine, :as => 'auth', which defaults to the lowercased and underlined version of the namespace. There are also other cases in this are:

  • calling hosted app's urls from an engine, in Rails it's done by using main_app helper
  • calling engine's urls from an engine - we need to decide if the namespace needs to be used to call urls or are templates in context of an engine treated specially, ie. they know about the namespace. In rails you don't have to use any namespace in such scenario, so the effect depends on the context, but I'm not sure if this would be a good option for Ember.js

Another thing to discuss is if an engine should be allowed to run without a host application. In Rails this idea was abandoned, because of various technical difficulties and design decisions that were made earlier, but it could be possible in Ember.js. The question is if this is something useful in Ember.js world and if it's hard to implement.

@lukemelia mentioned customisation, which is also something important. In my opinion overriding templates should work out of the box, so a properly namespaced template in the application should be rendered in the engine. On top of that, there should be a way to override any of the routes or controllers, possibly with ability to subclass them. For example I should be able to subclass LoginRoute and put the subclass in place of the LoginRoute. This is the least we can in order to not leave users with copying and pasting code or doing some other hacks.

Customisation is a hard topic in general, although it still looks much better than in Rails. Rails doesn't have any notion of view components out of the box. Instead, there are multiple ways to extend templates in engines, which mostly use a notion of annotations (you can annotate template with blocks, like header and then use it to insert content, for example before :header, "foo") or DOM manipulation (for example you can say `before '#header', 'foo').

drogus commented Oct 26, 2014

One thing that is not discussed in the proposal is handling links between an engine and an application. In the example of authentication, it's almost certain that you will need to link to login page of the engine from the host application, for example {{link-to "auth.login"}}. I'm not sure what should be the syntax and how the engine should be referred. In Rails there's an option to name an engine using :as option, like mount Authentication::Engine, :as => 'auth', which defaults to the lowercased and underlined version of the namespace. There are also other cases in this are:

  • calling hosted app's urls from an engine, in Rails it's done by using main_app helper
  • calling engine's urls from an engine - we need to decide if the namespace needs to be used to call urls or are templates in context of an engine treated specially, ie. they know about the namespace. In rails you don't have to use any namespace in such scenario, so the effect depends on the context, but I'm not sure if this would be a good option for Ember.js

Another thing to discuss is if an engine should be allowed to run without a host application. In Rails this idea was abandoned, because of various technical difficulties and design decisions that were made earlier, but it could be possible in Ember.js. The question is if this is something useful in Ember.js world and if it's hard to implement.

@lukemelia mentioned customisation, which is also something important. In my opinion overriding templates should work out of the box, so a properly namespaced template in the application should be rendered in the engine. On top of that, there should be a way to override any of the routes or controllers, possibly with ability to subclass them. For example I should be able to subclass LoginRoute and put the subclass in place of the LoginRoute. This is the least we can in order to not leave users with copying and pasting code or doing some other hacks.

Customisation is a hard topic in general, although it still looks much better than in Rails. Rails doesn't have any notion of view components out of the box. Instead, there are multiple ways to extend templates in engines, which mostly use a notion of annotations (you can annotate template with blocks, like header and then use it to insert content, for example before :header, "foo") or DOM manipulation (for example you can say `before '#header', 'foo').

@drogus

This comment has been minimized.

Show comment
Hide comment
@drogus

drogus Oct 26, 2014

One more thing that came to my mind in the topic of router is mounting engines on a dynamic routes. In Rails, you can do something like that:

resource :users do
  mount Blog::Engine
end

which allows to link to blog in the following way: link_to "Posts", blog.posts(user).

drogus commented Oct 26, 2014

One more thing that came to my mind in the topic of router is mounting engines on a dynamic routes. In Rails, you can do something like that:

resource :users do
  mount Blog::Engine
end

which allows to link to blog in the following way: link_to "Posts", blog.posts(user).

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 26, 2014

Member

which allows to link to blog in the following way: link_to "Posts", blog.posts(user).

this could be handled by subexpressions

Member

stefanpenner commented Oct 26, 2014

which allows to link to blog in the following way: link_to "Posts", blog.posts(user).

this could be handled by subexpressions

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Oct 26, 2014

Member

I'm confused between the difference of this RFC and ember-cli's already existing addons. Addons are effectively rails-like engines already. Beyond the engine-specific container I see a lot of overlap.

i think the idea is to formalize and improve this part of ember to further facilitate ember-admin and similar efforts.

@tomdale we should add ember-admin to our use-cases to review.

Member

stefanpenner commented Oct 26, 2014

I'm confused between the difference of this RFC and ember-cli's already existing addons. Addons are effectively rails-like engines already. Beyond the engine-specific container I see a lot of overlap.

i think the idea is to formalize and improve this part of ember to further facilitate ember-admin and similar efforts.

@tomdale we should add ember-admin to our use-cases to review.

@wagenet

This comment has been minimized.

Show comment
Hide comment
@wagenet

wagenet Oct 27, 2014

Member

I like this overall concept. Sounds like there are still some details to be resolved, but definitely a 👍 overall.

Member

wagenet commented Oct 27, 2014

I like this overall concept. Sounds like there are still some details to be resolved, but definitely a 👍 overall.

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Oct 27, 2014

Contributor

As a heads up, we're considering moving away from a "bubbling events" model to one where consumers of components pass in callbacks that can be called (sort of like React but without the verbosity).

Then rephrase my comment to say "we should consider using the same kind of interface that components use." Whether that's bubbling events or callbacks, I think there's a potential win here in reusing the same concept.

Contributor

ef4 commented Oct 27, 2014

As a heads up, we're considering moving away from a "bubbling events" model to one where consumers of components pass in callbacks that can be called (sort of like React but without the verbosity).

Then rephrase my comment to say "we should consider using the same kind of interface that components use." Whether that's bubbling events or callbacks, I think there's a potential win here in reusing the same concept.

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Oct 27, 2014

Contributor

I have another use case I'd like to see covered by this feature, please consider adding it to your list:

I have several small chunks of application that get reused in multiple places. They're good candidates to become Components, except they're rich enough that they want to load their own models and have child routes of their own. I would love to be able to mount them in all the places where they should work, and have all the right child routes and Route classes wired up correctly.

For example, I have a rich editor for a "Doctor's Order". It needs to be able to pop up in multiple different contexts whenever someone clicks on related content. It benefits from being a real Route so that I can use the router to load the right models, and it has child routes too to handle more complex situations when the user may want to dive deeper into related data.

Today I'm stuck metaprogramming my router map and Routes so that I can define them all once and then apply them underneath each resource that may need them.

(I don't want to link to a single canonical resource for this, because I render it inside the currently active top-level tab of my application, and it retains a lot of surrounding context. These different contexts really are different conceptual routes.)

Contributor

ef4 commented Oct 27, 2014

I have another use case I'd like to see covered by this feature, please consider adding it to your list:

I have several small chunks of application that get reused in multiple places. They're good candidates to become Components, except they're rich enough that they want to load their own models and have child routes of their own. I would love to be able to mount them in all the places where they should work, and have all the right child routes and Route classes wired up correctly.

For example, I have a rich editor for a "Doctor's Order". It needs to be able to pop up in multiple different contexts whenever someone clicks on related content. It benefits from being a real Route so that I can use the router to load the right models, and it has child routes too to handle more complex situations when the user may want to dive deeper into related data.

Today I'm stuck metaprogramming my router map and Routes so that I can define them all once and then apply them underneath each resource that may need them.

(I don't want to link to a single canonical resource for this, because I render it inside the currently active top-level tab of my application, and it retains a lot of surrounding context. These different contexts really are different conceptual routes.)

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Oct 27, 2014

Contributor

Loving where this idea is going. I could totally use this today for breaking up complex apps, or reusing duplicate code across non-related apps. Would be super nice with ember-admin 👍

Contributor

knownasilya commented Oct 27, 2014

Loving where this idea is going. I could totally use this today for breaking up complex apps, or reusing duplicate code across non-related apps. Would be super nice with ember-admin 👍

@atsjj

This comment has been minimized.

Show comment
Hide comment
@atsjj

atsjj Oct 27, 2014

Would this engine mechanism allow each composing app to attach itself to a different root element?

atsjj commented Oct 27, 2014

Would this engine mechanism allow each composing app to attach itself to a different root element?

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Oct 27, 2014

Contributor

Something like?

Router.map(function () {
  this.resource('admin', function () {
    this.mount('authentication', { path: 'auth' });
    // other resources/routes..
  });
});
Contributor

knownasilya commented Oct 27, 2014

Something like?

Router.map(function () {
  this.resource('admin', function () {
    this.mount('authentication', { path: 'auth' });
    // other resources/routes..
  });
});
@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Oct 27, 2014

Member

@atsjj I personally don't need that feature. My thinking is that you'd still share a single root element, but maybe you can expand on your use case a little?

Member

tomdale commented Oct 27, 2014

@atsjj I personally don't need that feature. My thinking is that you'd still share a single root element, but maybe you can expand on your use case a little?

@atsjj

This comment has been minimized.

Show comment
Hide comment
@atsjj

atsjj Oct 28, 2014

@tomdale I wouldn't say I have a use case for such a feature, but rather have a situation that kinda happened. I've been adding some new functionality with Ember to an existing Rails app and I've had to work around the problem of my Ember project not owning the entire DOM.

I wouldn't want to suggest that this kind of feature be added into this RFC, but rather if this feature just might implicitly happen given my understanding that an Engine is just another (Ember) app inside an app.

All that being said, I'd be happy to share the actual case with you offline if you're interested.

atsjj commented Oct 28, 2014

@tomdale I wouldn't say I have a use case for such a feature, but rather have a situation that kinda happened. I've been adding some new functionality with Ember to an existing Rails app and I've had to work around the problem of my Ember project not owning the entire DOM.

I wouldn't want to suggest that this kind of feature be added into this RFC, but rather if this feature just might implicitly happen given my understanding that an Engine is just another (Ember) app inside an app.

All that being said, I'd be happy to share the actual case with you offline if you're interested.

@tkellen

This comment has been minimized.

Show comment
Hide comment
@kristianmandrup

This comment has been minimized.

Show comment
Hide comment
@kristianmandrup

kristianmandrup Oct 28, 2014

For this to work, the first step is to integrate a way to use namespaces as per

https://github.com/rwjblue/container-resolver-namespaces

Then I guess it would need an upgrade to the router, to allow a new DSL namespace construct similar to the Rails router? When these corners are tackled, the rest follows...

For this to work, the first step is to integrate a way to use namespaces as per

https://github.com/rwjblue/container-resolver-namespaces

Then I guess it would need an upgrade to the router, to allow a new DSL namespace construct similar to the Rails router? When these corners are tackled, the rest follows...

@mike-north

This comment has been minimized.

Show comment
Hide comment
@mike-north

mike-north Oct 28, 2014

This is really exciting stuff guys!

A few use cases from Yahoo apps that may add to the discussion

"Peeking" into routes

In our role-based authentication scheme, one means of controlling access is on a per-route (and per API endpoint) basis. For example some users can edit customer information, so they're permitted to access the users.edit route and the PUT /users/:id API endpoint.

We have this mixin so that we can "peek" into a route to determine whether the user can transition to a given route, and we refrain from even attempting the transition if the user doesn't have access. A similar technique could be used to render only those links/actions that a user actually has access to.

import Ember from 'ember';

/**
 * Extends the default transitionToRoute to peak into the route
 * and only transition to the route if the user has the operations
 * mapped to the global user object
 */

export var transitionTo = function (routeName) {
    var get = Ember.get,
        route = this.container.lookup('route:' + routeName) || {},
        operations = Ember.get(this, 'user.operations') || {},
        routeOperation = get(route, 'operation');

    if (routeOperation && !get(operations, routeOperation)) {
        Ember.Logger.warn('Exited Transition.  Operation not found in the user operations');
        return false;
    }

    return this._super.apply(this, arguments);
};


export default Ember.Mixin.create({
    transitionTo: transitionTo
});

(credit to: @jasonmit)

This raises the question: In a world of async engines, when would route(s) owned by engines be loaded and resolvable?

more to come later

This is really exciting stuff guys!

A few use cases from Yahoo apps that may add to the discussion

"Peeking" into routes

In our role-based authentication scheme, one means of controlling access is on a per-route (and per API endpoint) basis. For example some users can edit customer information, so they're permitted to access the users.edit route and the PUT /users/:id API endpoint.

We have this mixin so that we can "peek" into a route to determine whether the user can transition to a given route, and we refrain from even attempting the transition if the user doesn't have access. A similar technique could be used to render only those links/actions that a user actually has access to.

import Ember from 'ember';

/**
 * Extends the default transitionToRoute to peak into the route
 * and only transition to the route if the user has the operations
 * mapped to the global user object
 */

export var transitionTo = function (routeName) {
    var get = Ember.get,
        route = this.container.lookup('route:' + routeName) || {},
        operations = Ember.get(this, 'user.operations') || {},
        routeOperation = get(route, 'operation');

    if (routeOperation && !get(operations, routeOperation)) {
        Ember.Logger.warn('Exited Transition.  Operation not found in the user operations');
        return false;
    }

    return this._super.apply(this, arguments);
};


export default Ember.Mixin.create({
    transitionTo: transitionTo
});

(credit to: @jasonmit)

This raises the question: In a world of async engines, when would route(s) owned by engines be loaded and resolvable?

more to come later

@kristianmandrup

This comment has been minimized.

Show comment
Hide comment
@kristianmandrup

kristianmandrup Oct 28, 2014

Perhaps engines could be loaded "by message", ie on transitionTo, if current loaded engines can't make transition it would load more engines and continue trying using some "clever" strategy? Doesn't feel very efficient however... Need examples to see what conventions would make most sense

Perhaps engines could be loaded "by message", ie on transitionTo, if current loaded engines can't make transition it would load more engines and continue trying using some "clever" strategy? Doesn't feel very efficient however... Need examples to see what conventions would make most sense

@samdelagarza

This comment has been minimized.

Show comment
Hide comment
@samdelagarza

samdelagarza Oct 28, 2014

@tomdale
-- I do like the use of callbacks instead of events to communicate between and engine and its host. But I would set strict APIs that didn't easily allow cross-communication outside of a predefined pipeline (events, messages, etc). I would prevent observables to participate in this sort of communication.
-- I would like local configs in the engines to be merged with engine-name.config in host app to finely tune configs
-- I would also like to see engines be bundled into two seperate files. 1. is a bootloader which gives all the meta-information needed to load the engine and is loaded with the ember app 2.) the second file contains the actual engine. The reason for the two files would be to load the app and engines quickly (async) and perhaps define some route info in the bootloader. Then when it's time to use it have an activator method that the bootloader async calls which loads the full engine file and activates it.
On a previous project we built a platform and we could load any app written by our team or third party devs. The bootloader file gave us a way to populate a dropdown with all available apps, without taking a hit when the app first loaded. Then when the user added the app to the screen it would "activate" and download the remainder of the app.

-- I would also allow the host application engine registry be dynamic. Our team had built the platform and was moving towards an app-store model with loading in of other apps at run-time.

the app i worked on was a wallstreet day trading application similar to e-trade or thinkOrSwim, so modular apps were a first-class citizen.

@tomdale
-- I do like the use of callbacks instead of events to communicate between and engine and its host. But I would set strict APIs that didn't easily allow cross-communication outside of a predefined pipeline (events, messages, etc). I would prevent observables to participate in this sort of communication.
-- I would like local configs in the engines to be merged with engine-name.config in host app to finely tune configs
-- I would also like to see engines be bundled into two seperate files. 1. is a bootloader which gives all the meta-information needed to load the engine and is loaded with the ember app 2.) the second file contains the actual engine. The reason for the two files would be to load the app and engines quickly (async) and perhaps define some route info in the bootloader. Then when it's time to use it have an activator method that the bootloader async calls which loads the full engine file and activates it.
On a previous project we built a platform and we could load any app written by our team or third party devs. The bootloader file gave us a way to populate a dropdown with all available apps, without taking a hit when the app first loaded. Then when the user added the app to the screen it would "activate" and download the remainder of the app.

-- I would also allow the host application engine registry be dynamic. Our team had built the platform and was moving towards an app-store model with loading in of other apps at run-time.

the app i worked on was a wallstreet day trading application similar to e-trade or thinkOrSwim, so modular apps were a first-class citizen.

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats Oct 29, 2014

Member

This is pretty great feedback guys!

@tomdale and I are going to integrate all of this and break out some stuff into separate proposals tomorrow.

Member

wycats commented Oct 29, 2014

This is pretty great feedback guys!

@tomdale and I are going to integrate all of this and break out some stuff into separate proposals tomorrow.

@kristianmandrup

This comment has been minimized.

Show comment
Hide comment
@kristianmandrup

kristianmandrup Oct 29, 2014

To me, the concept of engines seems a lot like the idea behind the proposed pods application structure. Why not have an engines folder under app, which simply becomes the "root" engine.

app
  /routes
  ...
  /engines
    /admin
    /posts
    /config

Then the app/routes would only contain the top level routes etc. In fact async engines could follow a similar approach as the current router model, where any engine (down the tree) would require higher level engines being instantiated before itself (so all engine dependencies are resolved and it has access to higher lv "scopes"). Hmmm...

I think this would be a much cleaner way to build apps and should be the default/recommended approach going forward .What do you think?

Not sure if or when it would make sense to have multiple levels of engines in a hierarchy, but why not?
Does it even make sense then to talk of an app with engines? why not an app with sub-apps?

app
  /routes
  ...
  /apps
    /admin
    /posts
     /apps
        /comments
    /config

I guess "engines" was just a Rails term - "train with an engine"? Why not leave all that Rails terminology behind? of course the above example should also allow for each /apps or app at any level to be extracted as an addon and then mounted at the appropriate place(s). Just some ideas...

To me, the concept of engines seems a lot like the idea behind the proposed pods application structure. Why not have an engines folder under app, which simply becomes the "root" engine.

app
  /routes
  ...
  /engines
    /admin
    /posts
    /config

Then the app/routes would only contain the top level routes etc. In fact async engines could follow a similar approach as the current router model, where any engine (down the tree) would require higher level engines being instantiated before itself (so all engine dependencies are resolved and it has access to higher lv "scopes"). Hmmm...

I think this would be a much cleaner way to build apps and should be the default/recommended approach going forward .What do you think?

Not sure if or when it would make sense to have multiple levels of engines in a hierarchy, but why not?
Does it even make sense then to talk of an app with engines? why not an app with sub-apps?

app
  /routes
  ...
  /apps
    /admin
    /posts
     /apps
        /comments
    /config

I guess "engines" was just a Rails term - "train with an engine"? Why not leave all that Rails terminology behind? of course the above example should also allow for each /apps or app at any level to be extracted as an addon and then mounted at the appropriate place(s). Just some ideas...

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Oct 29, 2014

Contributor

@kristianmandrup yeah that would make things easier, if everything was an app and apps can consume other apps. The learning curve would be much lower with this approach.

Love the way it would work with pods as well, it feels so natural.

Contributor

knownasilya commented Oct 29, 2014

@kristianmandrup yeah that would make things easier, if everything was an app and apps can consume other apps. The learning curve would be much lower with this approach.

Love the way it would work with pods as well, it feels so natural.

@kristianmandrup

This comment has been minimized.

Show comment
Hide comment
@kristianmandrup

kristianmandrup Oct 30, 2014

How about if Ember maintains a "skeleton structure" of the entire currently loaded app as a JSON graph.
This would allow for powerful runtime introspection and provide nice hooks for sub-apps and addons to "hook on to", either
compile or runtime.

This could open up for an easy way to achieve dynamic (asycn) loading of the app, such as mounting sub-apps, simply by attaching them to the app structure
when fully loaded.

Would have to be yet another tree of nodes...

  • parent()
  • children()
  • child(name, path or index) // get specific child
  • type() // controller, route etc.
  • name()
  • attach(nodes)
  • detach()
  • toGraph(type)

attach(node) could call onAttached(parentNode) of the specific artifact which could then execute whatever hook-on logic needed
while having knowledge as to where it is being attached (context).

async loading the app named admin and attaching it to the root app.

loadApp('apps/admin', target: '/')

Not sure if this would open up for a different type of resolver as well?

appStructure.toGraph('skeleton');

=>

{
  routes: {
    login: {
      mixins: ['simple-auth']
    }
  }
  controllers:
    login: {
      mixins: ['simple-auth']
    },
  ...
  apps: {
    admin: {
      routes:
      controllers: {
        posts: true,
        ...

      }
      ...

    },
    posts: true,
    ...

  }
}
appStructure.toGraph('full');

=>

Ember.App.full = {
  routes: {
    login: LoginRoute
  }
  controllers: {
    ...
  },
  ...
}  

How about if Ember maintains a "skeleton structure" of the entire currently loaded app as a JSON graph.
This would allow for powerful runtime introspection and provide nice hooks for sub-apps and addons to "hook on to", either
compile or runtime.

This could open up for an easy way to achieve dynamic (asycn) loading of the app, such as mounting sub-apps, simply by attaching them to the app structure
when fully loaded.

Would have to be yet another tree of nodes...

  • parent()
  • children()
  • child(name, path or index) // get specific child
  • type() // controller, route etc.
  • name()
  • attach(nodes)
  • detach()
  • toGraph(type)

attach(node) could call onAttached(parentNode) of the specific artifact which could then execute whatever hook-on logic needed
while having knowledge as to where it is being attached (context).

async loading the app named admin and attaching it to the root app.

loadApp('apps/admin', target: '/')

Not sure if this would open up for a different type of resolver as well?

appStructure.toGraph('skeleton');

=>

{
  routes: {
    login: {
      mixins: ['simple-auth']
    }
  }
  controllers:
    login: {
      mixins: ['simple-auth']
    },
  ...
  apps: {
    admin: {
      routes:
      controllers: {
        posts: true,
        ...

      }
      ...

    },
    posts: true,
    ...

  }
}
appStructure.toGraph('full');

=>

Ember.App.full = {
  routes: {
    login: LoginRoute
  }
  controllers: {
    ...
  },
  ...
}  
@lolmaus

This comment has been minimized.

Show comment
Hide comment
@lolmaus

lolmaus Sep 4, 2015

BTW, this proposal does not take into account possible HTML classname collisions.

It could be easily addressed by prefixing every classname with a namespace, but this means that both HTML templates and CSS styles should be processed. We can't do it unconditionally as it would ruin third-party classnames (e. g. Bootstrap). This means we have to provide means to mark certain HTML classnames as prefixable (namespaceable).

There is a simple and advanced way of doing this. A simple way is having a convention that any class that starts with, for example, engine-, will be automatically namespaced. An advanced way implies using a DSL to mark classnames prefixable. For example, <div class="@my-element grid2-4"> would be auto-converted to <div class="engine76f3d62-my-element grid2-4">.

The advanced way is much more convenient, but is hard to achieve due to the versatility of pre- and postprocessors on the styles side. Some of them will stumble upon the DSL. We will either need to preprocess uncompiled styles which implies support for numerous formats, or come up with a DSL that is accepted by all popular pre- and post- processors and at the same time remains more convenient than the simple way of enigine-.

The problem of HTML classname collisions between engines can also be addressed methodologically, without any new technology involved. But it will be a much harder way, and engines created in different teams can be found incompatible due to different class naming conventions.

lolmaus commented Sep 4, 2015

BTW, this proposal does not take into account possible HTML classname collisions.

It could be easily addressed by prefixing every classname with a namespace, but this means that both HTML templates and CSS styles should be processed. We can't do it unconditionally as it would ruin third-party classnames (e. g. Bootstrap). This means we have to provide means to mark certain HTML classnames as prefixable (namespaceable).

There is a simple and advanced way of doing this. A simple way is having a convention that any class that starts with, for example, engine-, will be automatically namespaced. An advanced way implies using a DSL to mark classnames prefixable. For example, <div class="@my-element grid2-4"> would be auto-converted to <div class="engine76f3d62-my-element grid2-4">.

The advanced way is much more convenient, but is hard to achieve due to the versatility of pre- and postprocessors on the styles side. Some of them will stumble upon the DSL. We will either need to preprocess uncompiled styles which implies support for numerous formats, or come up with a DSL that is accepted by all popular pre- and post- processors and at the same time remains more convenient than the simple way of enigine-.

The problem of HTML classname collisions between engines can also be addressed methodologically, without any new technology involved. But it will be a much harder way, and engines created in different teams can be found incompatible due to different class naming conventions.

@grapho

This comment has been minimized.

Show comment
Hide comment
@grapho

grapho Sep 4, 2015

Lots of communication here i like it! Good work everybody!

+1 for the newer RFC PR i saw to add route-less and route-able engines, I like the idea of an engine not initializing until its route is entered.

The software that I am developing will have a great need of loading separate modules(engines?) depending on user-type/access-level/subscription-type... has there been much communication about somehow supporting something like that?

I understand that keeping track of user related "status" is beyond the scope of "Engines".. however conditionally mounted engines is how i imagine something like that can work?

Is the "mounting" of engines compiled during the build of CLI? or are they able to be mounted or unmounted at runtime?

grapho commented Sep 4, 2015

Lots of communication here i like it! Good work everybody!

+1 for the newer RFC PR i saw to add route-less and route-able engines, I like the idea of an engine not initializing until its route is entered.

The software that I am developing will have a great need of loading separate modules(engines?) depending on user-type/access-level/subscription-type... has there been much communication about somehow supporting something like that?

I understand that keeping track of user related "status" is beyond the scope of "Engines".. however conditionally mounted engines is how i imagine something like that can work?

Is the "mounting" of engines compiled during the build of CLI? or are they able to be mounted or unmounted at runtime?

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Sep 5, 2015

Contributor

Since mounting happens inside renderTemplate, that means that it would be during runtime, so role based mounting would be possible.

Contributor

knownasilya commented Sep 5, 2015

Since mounting happens inside renderTemplate, that means that it would be during runtime, so role based mounting would be possible.

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Sep 6, 2015

Member

@lolmaus

If we agree that engines must be nestable, do we really need the distinction Application-Engine?

The only significant distinctions are that an Application is self-booting and maintains a router, while an Engine has a parent and might have dependencies. These distinctions could easily be suppressed to allow an Application to act as an Engine. I think it's very useful to think in terms of the composability of Engines and Applications (and I would add Components).

BTW, this proposal does not take into account possible HTML classname collisions.

I think this is a great example of a composability question that concerns Applications, Engines, and Components. I'd like to have these discussions with a broader scope (probably outside this RFC).

Member

dgeb commented Sep 6, 2015

@lolmaus

If we agree that engines must be nestable, do we really need the distinction Application-Engine?

The only significant distinctions are that an Application is self-booting and maintains a router, while an Engine has a parent and might have dependencies. These distinctions could easily be suppressed to allow an Application to act as an Engine. I think it's very useful to think in terms of the composability of Engines and Applications (and I would add Components).

BTW, this proposal does not take into account possible HTML classname collisions.

I think this is a great example of a composability question that concerns Applications, Engines, and Components. I'd like to have these discussions with a broader scope (probably outside this RFC).

@webark

This comment has been minimized.

Show comment
Hide comment
@webark

webark Sep 6, 2015

I read through the RFC, and tried to get through all of the comments, and everything is sounding great!
This may have already been answered, or might be to specific to implementation, so I apologize in advance if you need to repeat yourself.
I assume that ember and all dependencies will be shared, so that the whole application will only have one vendor.js file, and then engine specific js will added to the page conditionally based off of it has already been exicuted. (that might have been the difference between lazy loaded engine and not, but didn't see how the browser was going to deal with the files speced out anywhere). Are my assumptions correct?

webark commented Sep 6, 2015

I read through the RFC, and tried to get through all of the comments, and everything is sounding great!
This may have already been answered, or might be to specific to implementation, so I apologize in advance if you need to repeat yourself.
I assume that ember and all dependencies will be shared, so that the whole application will only have one vendor.js file, and then engine specific js will added to the page conditionally based off of it has already been exicuted. (that might have been the difference between lazy loaded engine and not, but didn't see how the browser was going to deal with the files speced out anywhere). Are my assumptions correct?

instance to interrogate its parent, specifically through its `RegistryProxy` and
`ContainerProxy` interfaces.
Alternatively, declarative dependencies can be defined on a limited basis. The

This comment has been minimized.

@chadhietala

chadhietala Sep 6, 2015

Member

This seems like it will become extremely fragile, however it is unclear to me how else to deal with Ember's runtime dependencies without making these types of dependencies more explicit and less magical.

@chadhietala

chadhietala Sep 6, 2015

Member

This seems like it will become extremely fragile, however it is unclear to me how else to deal with Ember's runtime dependencies without making these types of dependencies more explicit and less magical.

This comment has been minimized.

@dgeb

dgeb Sep 9, 2015

Member

I think that the fragility will come not from expecting a shared store service, but rather from expectations regarding the contents of that store. I think there are solutions to this problem, but they are specific to the data layer and beyond the scope of this RFC.

@dgeb

dgeb Sep 9, 2015

Member

I think that the fragility will come not from expecting a shared store service, but rather from expectations regarding the contents of that store. I think there are solutions to this problem, but they are specific to the data layer and beyond the scope of this RFC.

services. Should other types of dependencies be declaratively shareable?
Should addons be the recommended path to share all other dependencies?
## Async mounting of route-less engines

This comment has been minimized.

@chadhietala

chadhietala Sep 6, 2015

Member

I think there are some take aways here from the React Router 1.0.0-beta. It may also have some influence on other portions of this RFC. http://rackt.github.io/react-router/tags/v1.0.0-beta3.html#Advanced Usage

@chadhietala

chadhietala Sep 6, 2015

Member

I think there are some take aways here from the React Router 1.0.0-beta. It may also have some influence on other portions of this RFC. http://rackt.github.io/react-router/tags/v1.0.0-beta3.html#Advanced Usage

This comment has been minimized.

@dgeb

dgeb Sep 9, 2015

Member

Although I think async loading and initialization of engines will be our biggest win, even async loading and mounting of routing manifests could be valuable. In order to make this happen, we'll need to make resolution of route URLs async.

Always good to get insight into the same problems from another perspective. If there are any other lessons we should absorb from React Router, I'm all ears 😸

@dgeb

dgeb Sep 9, 2015

Member

Although I think async loading and initialization of engines will be our biggest win, even async loading and mounting of routing manifests could be valuable. In order to make this happen, we'll need to make resolution of route URLs async.

Always good to get insight into the same problems from another perspective. If there are any other lessons we should absorb from React Router, I'm all ears 😸

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Sep 9, 2015

Member

@webark

I assume that ember and all dependencies will be shared, so that the whole application will only have one vendor.js file, and then engine specific js will added to the page conditionally based off of it has already been exicuted. (that might have been the difference between lazy loaded engine and not, but didn't see how the browser was going to deal with the files speced out anywhere). Are my assumptions correct?

For the initial implementation, I'm proposing that engine dependencies are merged with application dependencies eagerly in a single vendor.js. Only engine-specific js will be lazily loaded.

In the future, once we figure out a good deduping strategy, we can also separately load deduped engine dependencies lazily.

Member

dgeb commented Sep 9, 2015

@webark

I assume that ember and all dependencies will be shared, so that the whole application will only have one vendor.js file, and then engine specific js will added to the page conditionally based off of it has already been exicuted. (that might have been the difference between lazy loaded engine and not, but didn't see how the browser was going to deal with the files speced out anywhere). Are my assumptions correct?

For the initial implementation, I'm proposing that engine dependencies are merged with application dependencies eagerly in a single vendor.js. Only engine-specific js will be lazily loaded.

In the future, once we figure out a good deduping strategy, we can also separately load deduped engine dependencies lazily.

@sandstrom

This comment has been minimized.

Show comment
Hide comment
@sandstrom

sandstrom Sep 9, 2015

Tangentially related (on loading of additional assets for engines)

With http/2 – which has rapid adoption[1] – concatenation isn't useful (or even harmful). I think the current application.js / vendor.js split is such an optimization. In short, with http/2 serving 50+ JS files (instead of a single large file) is not a problem any more.

Just something to keep in mind regarding dynamic loading of additional code/files.

[1] Currently at 60% globally, will reach ~70-80% within the next few months as Safari 9 and Windows 10 comes online.

More background:
https://http2.github.io/faq/#what-are-the-key-differences-to-http1x
https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdDwKJWNqWK1o4xMtYpKZCJYjM/present?slide=id.g518e3c87f_0_318
http://caniuse.com/#feat=http2

Tangentially related (on loading of additional assets for engines)

With http/2 – which has rapid adoption[1] – concatenation isn't useful (or even harmful). I think the current application.js / vendor.js split is such an optimization. In short, with http/2 serving 50+ JS files (instead of a single large file) is not a problem any more.

Just something to keep in mind regarding dynamic loading of additional code/files.

[1] Currently at 60% globally, will reach ~70-80% within the next few months as Safari 9 and Windows 10 comes online.

More background:
https://http2.github.io/faq/#what-are-the-key-differences-to-http1x
https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdDwKJWNqWK1o4xMtYpKZCJYjM/present?slide=id.g518e3c87f_0_318
http://caniuse.com/#feat=http2

@tomdale tomdale closed this Sep 21, 2015

Merge pull request #2 from dgeb/engines
Restore engines to tomdale/rfcs:master

@tomdale tomdale reopened this Sep 21, 2015

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Sep 21, 2015

Member

Sorry everyone 😬

Member

tomdale commented Sep 21, 2015

Sorry everyone 😬

@MiracleBlue

This comment has been minimized.

Show comment
Hide comment
@MiracleBlue

MiracleBlue Sep 21, 2015

Stop messing about @tomdale 😜

Stop messing about @tomdale 😜

@pixelhandler

This comment has been minimized.

Show comment
Hide comment
@pixelhandler

pixelhandler Oct 20, 2015

@dgeb - Does this engines proposal take into consideration various versions of Ember running in one app?

Some background… based on our upgrade experience with Ember from early pre-1.0 versions up through v1.12 (became impossible to upgrade to 1.13 for us.) my current refactor effort involves breaking down a big app to many smaller apps. (I’ve had to do that before as well, sometimes a screen at a time.) And, based on previous history with JS library dependencies in big JS apps (e.g. multiple version of jQuery with noConflict) -

I came to the conclusion that I do NOT want another big Ember app that has all it’s dependencies requiring compatibility with a single Ember version, either due to template compiling requirements or other constraint. Basically many smaller apps should afford the abilty to run various apps on various versions of Ember.

@dgeb - Does this engines proposal take into consideration various versions of Ember running in one app?

Some background… based on our upgrade experience with Ember from early pre-1.0 versions up through v1.12 (became impossible to upgrade to 1.13 for us.) my current refactor effort involves breaking down a big app to many smaller apps. (I’ve had to do that before as well, sometimes a screen at a time.) And, based on previous history with JS library dependencies in big JS apps (e.g. multiple version of jQuery with noConflict) -

I came to the conclusion that I do NOT want another big Ember app that has all it’s dependencies requiring compatibility with a single Ember version, either due to template compiling requirements or other constraint. Basically many smaller apps should afford the abilty to run various apps on various versions of Ember.

@tschoartschi

This comment has been minimized.

Show comment
Hide comment
@tschoartschi

tschoartschi Dec 21, 2015

The idea of engines sounds great to me 👍 Since the initial RFC is from October 2014 I wanted to ask what the current state of engines is and if there is already a plan when it is supposed to land in Ember. I'm really excited about engines 😃

The idea of engines sounds great to me 👍 Since the initial RFC is from October 2014 I wanted to ask what the current state of engines is and if there is already a plan when it is supposed to land in Ember. I'm really excited about engines 😃

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Dec 21, 2015

Member

@tschoartschi Work has begun on engines. The Engine and EngineInstance base classes have been extracted in emberjs/ember.js#12685

I'm now working with @rwjblue on an ember-engines addon that extends these new base classes and others in order to get engines working in ember-cli. We're aiming to make this addon public by the end of January. After concepts have been proven in this addon, we'll gradually roll the functionality into ember and ember-cli.

Member

dgeb commented Dec 21, 2015

@tschoartschi Work has begun on engines. The Engine and EngineInstance base classes have been extracted in emberjs/ember.js#12685

I'm now working with @rwjblue on an ember-engines addon that extends these new base classes and others in order to get engines working in ember-cli. We're aiming to make this addon public by the end of January. After concepts have been proven in this addon, we'll gradually roll the functionality into ember and ember-cli.

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Dec 21, 2015

Contributor

@dgeb where is this (addon) work happening?

Contributor

knownasilya commented Dec 21, 2015

@dgeb where is this (addon) work happening?

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Dec 21, 2015

Member

@knownasilya the addon work is happening in a private repo that will be made public in the next few weeks (certainly before the end of January).

Member

dgeb commented Dec 21, 2015

@knownasilya the addon work is happening in a private repo that will be made public in the next few weeks (certainly before the end of January).

@tsteuwer

This comment has been minimized.

Show comment
Hide comment
@tsteuwer

tsteuwer Jan 5, 2016

@dgeb any update on when the repo will be made public?

tsteuwer commented Jan 5, 2016

@dgeb any update on when the repo will be made public?

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jan 5, 2016

Member

@dgeb any update on when the repo will be made public?

@tsteuwer i suspect @dgeb's estimate to be still accurate

Member

stefanpenner commented Jan 5, 2016

@dgeb any update on when the repo will be made public?

@tsteuwer i suspect @dgeb's estimate to be still accurate

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Jan 14, 2016

Member

The ember-engines addon has been published. Although it should still be considered experimental, ember-engines does implement many of the concepts laid out in this RFC.

Feedback and contributions welcome.

Member

dgeb commented Jan 14, 2016

The ember-engines addon has been published. Although it should still be considered experimental, ember-engines does implement many of the concepts laid out in this RFC.

Feedback and contributions welcome.

@knownasilya

This comment has been minimized.

Show comment
Hide comment
@knownasilya

knownasilya Jan 14, 2016

Contributor

👏 for @dgeb and @rwjblue

Contributor

knownasilya commented Jan 14, 2016

👏 for @dgeb and @rwjblue

@sandstrom

This comment has been minimized.

Show comment
Hide comment
@sandstrom

sandstrom Jan 14, 2016

Great new! Awesome @dgeb and @rwjblue ⛵️

Great new! Awesome @dgeb and @rwjblue ⛵️

@sglanzer

This comment has been minimized.

Show comment
Hide comment
@sglanzer

sglanzer Jan 14, 2016

This is excellent news!

On Thu, Jan 14, 2016 at 1:59 PM sandstrom notifications@github.com wrote:

Great new! Awesome @dgeb https://github.com/dgeb and @rwjblue
https://github.com/rwjblue [image: ⛵️]


Reply to this email directly or view it on GitHub
#10 (comment).

This is excellent news!

On Thu, Jan 14, 2016 at 1:59 PM sandstrom notifications@github.com wrote:

Great new! Awesome @dgeb https://github.com/dgeb and @rwjblue
https://github.com/rwjblue [image: ⛵️]


Reply to this email directly or view it on GitHub
#10 (comment).

@oswaldoacauan

This comment has been minimized.

Show comment
Hide comment

awesome 👏

@danshapir

This comment has been minimized.

Show comment
Hide comment
@danshapir

danshapir Mar 5, 2016

Amazing job! Any thought on when will this addon will work on Beta/Release? Or even better, when will it be merged with Ember itself?

Amazing job! Any thought on when will this addon will work on Beta/Release? Or even better, when will it be merged with Ember itself?

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Mar 5, 2016

Member

when will it be merged with Ember itself?

when its ready

Member

stefanpenner commented Mar 5, 2016

when will it be merged with Ember itself?

when its ready

@danshapir

This comment has been minimized.

Show comment
Hide comment
@danshapir

danshapir Mar 6, 2016

@stefanpenner well that's obvious. The question is do we have a timeline? Like ember 2.7 for example...

I would like to assist, but to get the funding from my company I need a timeline (not a strict one, just a give or take).

@stefanpenner well that's obvious. The question is do we have a timeline? Like ember 2.7 for example...

I would like to assist, but to get the funding from my company I need a timeline (not a strict one, just a give or take).

@mixonic

This comment has been minimized.

Show comment
Hide comment
@mixonic

mixonic Mar 28, 2016

Member

@danshapir Implementation work has largely been lead by @dgeb and @rwjblue. I suggest looking into issues at https://github.com/dgeb/ember-engines/issues and reaching out to them for context. If you have the time you can definitely make an impact.

Member

mixonic commented Mar 28, 2016

@danshapir Implementation work has largely been lead by @dgeb and @rwjblue. I suggest looking into issues at https://github.com/dgeb/ember-engines/issues and reaching out to them for context. If you have the time you can definitely make an impact.

@timkendall

This comment has been minimized.

Show comment
Hide comment
@timkendall

timkendall Apr 4, 2016

Has anyone put much thought into how or if one should define styles in an engine? The issue is styles from the parent app could easily smash those defined in the engine. It seems like something cool could be done with ember-css-modules.

The simple solution is to utilize the default, scoped behavior of CSS Modules to avoid conflicts altogether (or at least keep them to a minimum). Another option (that could be used in conjunction with this approach) is to have a way of defining styles in a component or controller that need to be fulfilled by the engine consumer. This would be pretty trivial seeing as how Ember-CSS-Modules already exposes a styles member on each controller and component.

Anyways I may be overthinking this but we recently ran into this problem in a project I was working on with engines.

Has anyone put much thought into how or if one should define styles in an engine? The issue is styles from the parent app could easily smash those defined in the engine. It seems like something cool could be done with ember-css-modules.

The simple solution is to utilize the default, scoped behavior of CSS Modules to avoid conflicts altogether (or at least keep them to a minimum). Another option (that could be used in conjunction with this approach) is to have a way of defining styles in a component or controller that need to be fulfilled by the engine consumer. This would be pretty trivial seeing as how Ember-CSS-Modules already exposes a styles member on each controller and component.

Anyways I may be overthinking this but we recently ran into this problem in a project I was working on with engines.

@MiguelMadero

This comment has been minimized.

Show comment
Hide comment
@MiguelMadero

MiguelMadero Apr 4, 2016

@timkendall styles are a bit challenging due to the potential of overlaps, certainly tools or libs like css-modules can help, in our case, we're relying on well-scoped rules.

I don't think this problem is specific to engines, add-ons and any other vendor styles that you pull-in have the same issue. That said, engines, once we get lazy loading make the problem a bit harder. Today we could rely on load order and test it to make sure we get the expected behavior (e.g. app styles load after vendor styles or component styles are imported in a certain order). Then we could either fix the scoping or change the order to get the desired behavior. With lazy loading, the order depends on which routes are visited first, making the style order non-deterministic and extremely hard to test since the permutations can be really high even with a small number of engines (n!).

@timkendall styles are a bit challenging due to the potential of overlaps, certainly tools or libs like css-modules can help, in our case, we're relying on well-scoped rules.

I don't think this problem is specific to engines, add-ons and any other vendor styles that you pull-in have the same issue. That said, engines, once we get lazy loading make the problem a bit harder. Today we could rely on load order and test it to make sure we get the expected behavior (e.g. app styles load after vendor styles or component styles are imported in a certain order). Then we could either fix the scoping or change the order to get the desired behavior. With lazy loading, the order depends on which routes are visited first, making the style order non-deterministic and extremely hard to test since the permutations can be really high even with a small number of engines (n!).

@dgeb dgeb merged commit 2d2cc97 into emberjs:master Apr 11, 2016

@dgeb

This comment has been minimized.

Show comment
Hide comment
@dgeb

dgeb Apr 11, 2016

Member

Merging finally 🎉

We should continue to refine Ember's public API related to engines via subsequent RFCs. And we are tracking issues and progress in the ember-engines addon.

Member

dgeb commented Apr 11, 2016

Merging finally 🎉

We should continue to refine Ember's public API related to engines via subsequent RFCs. And we are tracking issues and progress in the ember-engines addon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment