EAK: Is it possible to "lazy load" modules?


#1

In Ember App Kit, is there the possibility to “lazy load” modules, so that I can have a “core” module which contains all necessary code to run the application and it’s common features and some other modules which are only fetched from the webserver if the user enters a specific route?

This would be awesome as the size of the downloaded files would be a lot smaller on the first visit and only code which is really necessary for the user to use the app is downloaded. This is not only a big benefit for huge applications but it would also make sense if you think of users accessing the application through mobile devices with slow/slowest internet connection, etc.


#2

Yeah! This actually reminds me of an example from the docs. Here’s a snippet from Ember.Route.beforeModel:

App.PostRoute = Ember.Route.extend({
  beforeModel: function(transition) {
    if (!App.Post) {
      return Ember.$.getScript('/models/post.js');
    }
  }
});

This will load the script that provides App.Post. This is a good way to load big third-party libraries only when needed in a particular route.


#3

thanks a lot, so this is the approach to load the “module” I want (I guess it’s best suited in the #beforeModel hook like in your example), but I would also need the “module” to be transpiled/compiled/packed as such, so that I can load the whole “module” once the user wants to access the said route. is this also possible?


#4

Is it possible? Of course. Is it simple or easy? Probably not. You’d have to manually set up a separate system that would do that compilation/packaging for you. That would introduce another point of failure into your application that probably isn’t worth it. Unless your modules are dynamic and changing at runtime, it would be much easier to have all of that done at development time.


#5

@macu’s suggestion is a great one, and it will get you pretty far.

Here are some things we’re discovering as we go down this path:

Idempotent Loading

You’ll want some way of remembering which code has been loaded already. The Container and IndexRoute are both decent places to record this. Or you can build a ModuleLoaderService that remembers what’s been loaded. That’s particularly useful if modules can declare hard dependencies on other modules.

May-Fail & Delay-Load Modules

You may find cases where a module is optional or at least non-critical. That is, without it, some feature of the app won’t work, but you don’t necessarily want to block the user from using the rest of the app if it’s not available. Alternatively, you might want to let the app boot without the module, then fill in the missing pieces later. There are a few tools that will help here.

{{outlet}}s in your layout

In your root view, you might have {{outlet recent-tweets}}. Then, in the recent_tweets.js module, you get a reference to that root view and call connectOutlet('recent-tweets', RecentTweetsView.create()); to inject the actual content.

Null controllers & services

If module A wants some information from module B (say, a BController), but B isn’t guaranteed to be loaded before A, A can define a NullBController that B replaces. For example, A calls container.register('controller:b', NullBController);. Then, B calls container.unregister('controller:b'); container.register('controller:b', RealBController);.

The problem here is that if A has already asked for an instance of this, it’s hard for B to reach in to A to swap in the real one. The user may not see the B functionality until they leave the current route and re-enter it. Liberal use of events (particularly on the router) may help with this; we haven’t explored that option.

Hot-Upgrading Templates & Views

This is about as tricky as the previous. Say someATemplate has {{outlet b-content}}. The ARoute can call this.render('someBTemplate', { outlet: 'b-content' });, but if B isn’t loaded, render won’t be able to find someBTemplate and will raise an assertion error. A can compensate by defining a nullSomeBTemplate. When B loads, it might be able to

  1. unregister the nullSomeBTemplate
  2. register the real one
  3. ask the router for the current route
  4. call route.render('someBTemplate', { outlet: 'b-content' }) to upgrade the view

Summary

I think there are promising things in this area, but we don’t yet have all the answers. I’d love to see the community build an add-on that solves some of the above problems. I’d be happy to help.


#6

Oh, and two more related items:

Module Initialization

In our app, each module Foo can register a moduleInitializer:foo that the ModuleLoader will call once the code is ready. That provides a hook for the module to do things like connectOutlet or manipulate the Container.

Module Isolation

So far, we’re passing App.__container__ (accessed as this.container from the route) to each module’s initializer. We’re not certain whether this is a good strategy in the long-term. It might be wise to pass a new Ember.Container instance that is isolated to the module, possibly with a few services added in. For example, the module might need access to the main app’s router or a push-notifications channel.


#7

I would keep an eye on this.


#8

thanks a lot for your replies @jamesarosen & @chadhietala - I’ll keep an eye on Ember-CLI and will follow along the path which is suggested. I’ll keep ya posted what I could come up with :smile: