ember-engines icon indicating copy to clipboard operation
ember-engines copied to clipboard

Mounting root engines

Open mschinis opened this issue 8 years ago • 6 comments

Hey all,

I started recently breaking our website into multiple engines and have come across an interesting bug that I'm sure a lot of people will come across.

I've been trying to debug this problem, where I'm not really sure if this is a problem of router.js or ember-engines. Apologies for the long issue below but the more information, the easier it would be for anyone else to reproduce/understand the issue.

What's the problem?

The ability to break an ember app into a multiple engines is perfect. This gives us (Trouva) the ability to serve our clients with smaller payloads and provide a faster experience in general. Breaking it up so far has been really smooth but when I started to create an engine with multiple routes the app started crashing because of clashing index ("/") routes.

Working with one root engine

Consider the following route files. Repository here

// file: host-app/router.js
Router.map(function() {
    this.mount('one', { path: '/' });
    this.mount('two');
});
// file: lib/one/addon/routes.js
import buildRoutes from 'ember-engines/routes';
export default buildRoutes(function() {
  this.route('one-one');
  this.route('one-two');
});
// file: lib/two/addon/routes.js
import buildRoutes from 'ember-engines/routes';
export default buildRoutes(function() {
  this.route('two-one');
  this.route('two-two');
});

The above will generate the following working route structure:

/             <- will render lib/one/addon/templates/application.hbs
/one-one
/one-two
/two/two-one
/two/two-two

Working with two root engines

Changing the mounting setup on the host-app however, will breaking the website.

// file: host-app/router.js
Router.map(function() {
    this.mount('one', { path: '/' });
    this.mount('two', { path: '/' });
});

The route structure I would expect to see here, would be:

/             <- should render lib/two/addon/templates/application.hbs
/one-one
/one-two
/two-one
/two-two

Instead, the / routes of the two routes clash, causing router.js to crash when it tries to map. Looking into this further, only the following routes were recognised. (Collapsed routes are loading and error dummy states)

If there is a workaround, it would be greatly appreciated, but for now it seems like this is either a bug in how ember-engines generates the route structure or how router.js handles the routes it's given.

mschinis avatar May 05 '17 13:05 mschinis

In general, mounting more than one Engine a single URL will not work. That said, I'd like to understand the thought process here.

Given your "two root engines" example, why would you expect the two engine to be rendered when visiting /? Or, rather, what URL would you expect to render the one engine's application route?

trentmwillis avatar May 05 '17 16:05 trentmwillis

@trentmwillis In my example above, I was actually visiting the route /two-two, hence I expected the two engine to be rendered.

I think it makes sense to render the one/.../application.hbs template when you visit a route inside the one engine, and the two/.../application.hbs when you visit a route inside the two engine.

Our usage is actually pretty simple. We have pages like /sale, /new-in, /category/<category-name> which all include very similar logic. It makes sense to be able to bundle all of them together into a single engine and mount it on the root of the project.

Doing so however, breaks the app. It even breaks, if you don't mount a second root engine and define another route at / like so:

export default Router.map(function() {
    this.route('homepage', { path: '/'});
    this.mount('some-engine', { path: '/' });
});

mschinis avatar May 05 '17 17:05 mschinis

I understand the issue, but there is a detail that I think you're overlooking. What happens when you visit /? Given that / is a single URL (resource location), it does not make sense that it could map to two different resources (Engines in this case).

trentmwillis avatar May 05 '17 17:05 trentmwillis

I think it's worth adding an option to opt-out of the root path of each engine. This could make the / path of each engine act as an unrecognised route. If I wanted for example, to create an engine called shop which is not a root engine, I should be able to configure the following routes:

/shop/category/category-name
/shop/tag/tag-name

without having a /shop route. Similarly, for root engines I believe it would be quite powerful having the option to opt out of the / route, and be able to mount multiple root engines.

mschinis avatar May 08 '17 11:05 mschinis

I'm also interested in this. A good example use case for this, is mounting an engine for static pages. With the current route structure each static page has to be mounted in it's own engine which is an anti-pattern for DRY code. The root path could be set with a flag such as:

this.route('login', { path: '/' });
this.mount('static-pages', { path: '/', rootNamespace: false });

This would work where rootNamespace is true by default, but could be overridden using a false flag. It works like resetNamespace, but effectively in reverse. So if your engine routes.js file was:

import buildRoutes from 'ember-engines/routes';

export default buildRoutes(function () {
  this.route('terms');
  this.route('privacy');
});

You could effectively have static-pages namespace hanging from the root url without /static-pages/ in the path.

ezy avatar Aug 19 '20 23:08 ezy

See also https://discuss.emberjs.com/t/mounting-two-engines-in-ember-engines/16237

ezy avatar Aug 20 '20 00:08 ezy