ArcGIS Hub goes esri-loader: performance wins and lessons learned

Today’s ArcGIS Hub Changelog included this entry: “Sites & pages now load ~50% faster when a map is not present”. We achieved that by migrating ArcGIS Hub code base over to using esri-loader (via ember-esri-loader) to lazy load the ArcGIS API for JavaScript only when it’s needed (i.e. only on pages that have a map).

ArcGIS Hub & esri-loader logos

This was the largest such migration that I’ve taken on to date, and I learned a few things in the process that I thought are worth sharing. Although this post is about an Ember.js application, all but the last section equally apply to applications developed with other frameworks.

Even helps with pages that have maps

We expected the time it takes for pages without maps to decrease, and as stated above, it did dramatically. However, we were surprised to discover that even pages with maps experienced a slight drop in initial load time (~1 second over 3G), especially after we took advantage of esri-loader’s new loadCss() function to lazy load the ArcGIS CSS.

Push your dependencies to the edge of your application

The ArcGIS Hub is actually split into two applications (the public facing pages and an admin interface) with 5 different maps between them, and has been built over the course of a few years by a dozen different developers. In other words, the code base was littered with over 50 import { something } from 'esri/something' in over a dozen different files. Since you can’t use such statements with esri-loader, before I could even install esri-loader, I’d have to deal with those. I decided to embrace the best practice of pushing your dependencies, in this case the ArcGIS API, to the edge of your application and move those import statements into a single(ton) service module that exposes functions that other modules (like components) can use access the esri modules.

Another benefit of consolidating all the map related code into a single service is that it has already made it easier for us to write tests for map components; now we can just stub the new service.

Working with asynchronous modules synchronously

Once the import statements were consolidated, the biggest challenge was replacing code that had previously used modules synchronously with code that uses esri-loader’s asynchronous  loadModules(). For example, let’s say a component had import Graphic from 'esri/graphic' and later includes a line like  let graphic = new Graphic(graphicJson). It would be cumbersome to replace that with something like:

// loadGraphic() calls loadModules(['esri/graphic'])
mapService.loadGraphic(graphicJson).then(graphic => {
  // now use the graphic instance
});

This one of the few (only?) drawbacks of using esri-loader, and I had previously come up with a pattern that can be used to address it. I applied that pattern in this migration and discovered that it actually does scale well, so I decided to include a mixin in ember-esri-loader to help others adopt it in their own apps. Although the above pattern is a not as easy to implement as just importing whatever you want whenever you want, it forces you to think about when you can/should load potentially expensive dependencies.

Going from import to esri-loader is harder than the other way around

I think another reason developers like they way ember-cli-amd lets you import esri modules is because it feels future proof. The idea is that one day the ArcGIS API will just work with import statements and we can just remove the library that makes them work today. It may work out that way, but who knows when that day will be? In the mean time users of your application are probably taking an unnecessary performance hit. If at some point ArcGIS Hub no longer needs esri-loader (and I hope that day comes sooner than later), it will be easier for us to go back to importing esri modules than it was to migrate from scattered import statements to esri-loader. We can just remove the calls to loadModules() and add back the import statements in one file. So, no, esri-loader is not future proof, but because it forces you to adopt best practices today, it won’t be hard to remove later when the time comes.

Potentially unlocking Ember’s full potential

Before switching over to ember-esri-loader, we had abandoned our acceptance test suites. When our app was built using ember-cli-amd, we would see intermittent script errors when running acceptance tests that made them too flakey to rely on. The errors appears to have been due to the map or ArcGIS API not being cleaned up (destroyed) properly between tests. So far with with ember-esri-loader, our acceptance tests have been running reliably (knock on wood).

More importantly, we now have the potential to one day start server-side rendering our app since we can control when the ArcGIS API is loaded and used (i.e. not on the server). In addition to helping with page load performance, server-side rendering will help with SEO and enable rich content (title, description, image, etc) in social media shares. I had previously done some experimentation in getting esri-loader to work with Fastboot, and we’re now working on ways to make that work without upstream changes to Fastboot so we can start using Fastboot to render our application on the server. Once we’ve got that working I’ll follow up with another blog post.