Lazy Loading Ember controllers, view, and templates


#1

Hi all. This is my first time bringing up an article on this forum. Please let me know if I’m not following the generally accepted conventions.

My situation: I have an app that has approx 15-20kloc not including external dependencies. Currently I am using Brunch as a build tool. The problem with this is that it compiles everything in my app (app, routes, controllers, views, templates, etc.) into one single file that needs to be loaded on startup. Minified, just the app.js file generated by Brunch is about 1.25MB. And it’s only going to get bigger. That app.js combined with jQuery, Ember, Ember Data, Handlebars, and other libraries combines for close to 2MB. I am aware the enabling gzip on my server will help a lot but that initial app load will still be in the 300-400KB range.

It makes sense to me to not load the resources a particular route needs until the route is accessed. For that reason, I am attempting to switch things over to a lazy-load policy. My attempts are going to be based on this article: http://madhatted.com/2013/6/29/lazy-loading-with-ember. In order to do this, I will need my controllers, views, and templates (and any other dependencies) each in separate files to be loaded on the fly. With Brunch I get the flexibility for “brunch watch” to automatically compile things and deploy to an output (including pre-compiling *.hbs files into .js).

I am looking for any thoughts, suggestions, and/or solutions for accomplishing this lazy loading ability with the following:

  • Ability to “watch” for app changes to automatically re-build
  • Option to build minified js even when there is no concatenation
  • Precompile handlebars templates and output to their own files (also w/ option to be minified - although this isn’t that important because you can’t really debug hbs files)

I know I can accomplish the minification and handlebars compiling with my own script with uglify and handlebars but I also value the immediate builds when files change (especially when styling a page and making frequent changes). I can’t see an easy way to “watch” my app for changes while also pre-compiling hbs and minifying.

I am open to any process. I’d appreciate any help.


#2

Would love to hear thoughts as this is a common issue for large deployments~ :smile:


#3

I’d be curious to hear if something like this has been done before. I know Zendesk has 1MB+ download size and I haven’t heard them doing this.

I haven’t worked with Brunch, but I heard it’s being explored by Ember App Kit as a potential build system to use instead of Grunt. Although, we might forgo Brunch in favour of Broccoli by @joliss.

With ES6 this might be easier to do because you explicitly specify requirements for every file. In EAK, the loader.js provides the require() function that’s used to import requirements, it could be changed to make a request if requirements are not available.


#4

Even though Ember is not yet mobile ready, supporting asynchronous code loading for each route would be a big help toward this goal. The little added complexity goes a long way with the usability of a website. 1.2MB wouldn’t be terrible but because my current app is on a server in Hong Kong, the latency + download size eats up a big chunk of time. Even on a server in the U.S. on an average internet connection (let’s say 15Mb/s) + latency + other factors (wireless, bandwidth with other devices/programs) it could take 3 - 5 seconds to get everything to load. That’s pushing the boundaries of being acceptable. Ember touts being easy to create a large app with (and rightly so!) but right now a large app means terrible initial loading.

I am debating the best way to start my next project because I know it’ll be a large app (even bigger than the one I referenced above) and I’ll want to lazy load code. I know I can manage it with a couple of my own custom scripts or to list out each controller, view, and template in my coffee.config file but I lose the quick compilation and deployment that something like brunch watch or grunt server does for me. Tweaking templates and styles would be a nightmare to constantly add extra steps into the process (for manually compiling *.hbs mostly but also minification).

I might look into developing a solution using EAK with my own flavor added. Trouble is, I am not too familiar with doing things with Grunt or NodeJS. Would anyone be interested in starting a pull request with me? I think loader.js solution would be perfect, however, this can easily be done with a custom solution in code. The problem here mostly revolves around the build process for efficient development.

UPDATE: I have come across an article relating to concatenating rules that may help keeping a good process but with the ability to dynamically load scripts as-needed… http://stackoverflow.com/questions/19648095/yeoman-grunt-and-requirejs-dont-concatenate-optimize-output which references https://github.com/yeoman/grunt-usemin…thoughts?


#5

You can lazy load code using any of the router’s beforeModel/model/afterModel hooks. There is a jsFiddle/JSBin example floating around somewhere. The one exception is you have to have all your routes loaded upfront.


#6

@ebryn Thanks for your tip.

I actually have had some success with this. I am still using Brunch but specifying my controllers, views, and templates to be compiled individually and then each route specifies its “lazyScripts” which the mixin uses to fetch in the “beforeModel” hook.

beforeModel: function(){
  var promises = [];
  this.get('lazyScripts').forEach(function(scriptName){
    if (!App.LazyLoaderMixin.loaded[scriptName]) {
      promises.push($.ajax({
        crossDomain: window.ENV.DEVELOPMENT,
        dataType: 'script',
        url: 'scripts/' + scriptName + '.js'
      }));
    }
  });

  var thiz = this;
  return Ember.RSVP.all(promises).then(function(){
    var scripts = thiz.get('lazyScripts');
    scripts.forEach(function(scriptName){
      App.LazyLoaderMixin.loaded[scriptName] = true;
      require(scriptName);
    });
  });
}

This seems to be doing this trick fairly well. The only issue I have is related to the ember-handlebars-brunch compilation of templates and registering with ember with a backslashes ("") instead of forward slashes. The concept so far has been working brilliantly. I have also heard that AMD may be a better alternative to .ajax or .getScript.


#7

I’m experiencing this problem in about every second ember app, basically because they’re huge admin panels, with a lot of routes, views, components and etc. I was thinking for something like following: currently ember use the finger print for static content, we can expand this and create a finger prints for all templates, since they’re the biggest ( at least in my case ). Then, when the model hook is fired, we can load at the same time both the model and the compiled template (yes the template needs to be compiled on the server), but this needs to happen behind the scenes. If there is no model specified in the route, ember should load the template. I’m ready to get dirty in this and help with a PRs, but just after we all agree which is the best solution for this. I believe this will be e huge feature for ember. The template compilation will be the same like now, with the difference that every template.hbs will create a template-name.js file with a finger print in lets say /dist/templates. I forgot to mention that this will require ember-cli, since most of the things are already done there.