fbpx

Blogs from the Ranch

< Back to Our Blog

Is Your Ember App Too Big? Split it up with Ember Engines, Part 3

In the previous two posts on Ember Engines, we started building an Ember “umbrella” application for mounting a couple Ember Engines. You can catch up on the series in Part 1 and Part 2. Ember Engines help a development team to separate UX concerns and split compiled files into separate .js bundles that can be lazily loaded. When developing Engines, you can create an in-repo-engine or an Ember Addon as an external repository.

In this post, you will share links and services, like store and session, between the parent app and the engines. That way, Engines can integrate with the parent app’s model and authentication layer without duplicating the Model, Adapter and Serializer classes defined in the parent app. The parent application will also control where engine routes are mounted.

Within an Engine, links to internal routes work the same as any other Ember application: {{link-to "Home" "application"}}, {{#link-to "application"}}{{/link-to}}, [route].transitionTo('application') and [controller].transitionToRoute('application').

To add external links to an engine app, there are new components and methods: {{#link-to-external}}{{/link-to-external}}, {{link-to-external}}, [route].replaceWithExternal and [route].transitionToExternal. In the external-admin engine, add some links to the posts template:

<!-- external-admin/addon/templates/posts.hbs -->
<h1>List of Posts</h1>
<ul>
  <li>Post 1</li>
  <li>Post 2</li>
  <li>Post 3</li>
  <li>Post 4</li>
</ul>
{{#link-to-external "home"}}Go home{{/link-to-external}}
{{#link-to-external "blog"}}Go to Blog{{/link-to-external}}
{{outlet}}

We now have external links to “home” and “blog”. These will navigate the user to the parent app’s “home” route and the “blog” route from another engine. Now our external-admin engine needs to export these dependencies to the parent application. You will need to add those keys to the engine definition in external-admin/addon/engine.js:

 const Eng = Engine.extend({
   modulePrefix,
-  Resolver,
+  Resolver,
+  dependencies: {
+    externalRoutes: [
+      'home',
+      'blog'
+    ]
+  }
 });

Once you have committed these changes, you have to reinstall the engine in the parent application.

cd ../large-company-site
ember install ember install https://github.com/bignerdranch/ember-engine-external-admin.git

In the parent application’s package.json file you can check the commit revision hash against the latest revision from your engine’s GitHub page to make sure you loaded the latest:

"external-admin": "git+https://github.com/bignerdranch/ember-engine-external-admin.git#5e3601eebfcb4e82477b1f6e7e75355df8cbd1a1",

The commit hash (everything after “#” sign) tells you which revision of external-admin your parent app is loading. Packages often use release numbers or tags instead of hashes.

Now that your engine has declared its dependency on the parent app’s links and other engine routes, you need to “inject” these dependencies into the engine. Add the following to large-company-site/app/app.js:

 App = Ember.Application.extend({
   modulePrefix: config.modulePrefix,
   podModulePrefix: config.podModulePrefix,
-  Resolver
+  Resolver,
+   engines: {
+     externalAdmin: {
+      dependencies: {
+        externalRoutes: {
+          home: 'application',
+          blog: 'in-app-blog.posts'
+        }
+      }
+    }
+  }
 });

These dependency mappings allow the parent app to fully control the engine’s external routing to the parent app, or to another engine!

Share Services

Services are shared in exactly the same way as links: by adding a dependency mapping to a local object in the parent application. If you want your engine to retrieve data via the parent application’s store, you can add a dependency to the external-admin/addon/engine.js:

 const Eng = Engine.extend({
   modulePrefix,
   Resolver,
   dependencies: {
+    services: [
+      'data-store'
+    ],
     externalRoutes: [
       'home',
       'blog'
     ]
   }
 });

This data-store dependency can be accessed like any other service, this.get('dataStore').*, but it will need to be added to each route, component or controller with Ember.inject.service():

export default Ember.[Object Type].extend({
  dataStore: Ember.inject.service(),
  fakeFunc() {
    this.get('dataStore').[store method]()
  }
});

Finally, the service dependency needs to mapped from the parent to the engine in large-company-site/app/app.js:

 App = Ember.Application.extend({
   modulePrefix: config.modulePrefix,
   podModulePrefix: config.podModulePrefix,
   Resolver,
   engines: {
     externalAdmin: {
       dependencies: {
+        services: [
+          { 'data-store': 'store' }
+        ],
         externalRoutes: {
           home: 'application',
           blog: 'in-app-blog.posts'
         }
       }
     }
   }
 });

Here, we mapped the engine’s data-store service to the parent app’s store service.

You can use this technique to share any service from your parent app to an engine. The parent application needs to inject all the dependencies required to load an engine, which should be documented in the engine repo’s README.md. Once you know the dependencies, you can map your routes and services to the engine. And if you make changes to the parent app’s services or routes, you just have to update the dependency mapping in app.js!.

And best yet, the structure of your build, environment, development or deployment process is not affected by this giant shift. It’s still just an Ember app with some Ember Addons! So whether you are splitting an existing application for better performance or creating teams for a complex ground-up application, Ember Engines are the solution you’ve been waiting for.

Happy building!

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project