Using the ArcGIS API for JavaScript in Applications built with webpack

It seems like webpack is becoming the most popular module bundler in the Angular 2 and React communities. It is not (yet) possible to have webpack directly load modules from the ArcGIS API For JavaScript. However, the ArcGIS developer community has come up a couple innovative workarounds that make it easy to add ArcGIS maps and services to applications built with webpack. Unfortunately I’ve seen a few developers run into trouble because they don’t understand how these abstractions work.

In this post, I’ll explain the hack that underlies these solutions and explore the pros and cons of two patterns for loading ArcGIS modules in applications that are built with a module bundler. Although most of the examples shown use webpack, the same patterns can be applied to rollup.js as well.

The problem

The ArcGIS API for JavaScript is written in Dojo and distributed as large library of AMD modules. Unfortunately, most module loaders, including webpack, implement the AMD “standard” differently than the way that Dojo does, specifically in the area of plugins. There is a Dojo loader for webpack that tries to address those differences, but as of time of writing it is not capable of loading ArcGIS modules. Even once that issue is resolved, I still think you’ll want to understand the workarounds below so you can decide which is the best solution for your application.

Solutions you can use today

The ArcGIS developer community has come up with a few solutions to this problem that are based on a common underlying hack. In one form or another, all of these solutions:

Exclude ArcGIS API modules from any bundling done by other module loaders

You can of course do all of that without any special library. The libraries and boilerplates that developers have created provide varying amounts of syntactic sugar that hide this hack from developers. I’m going to group these solutions into 2 patterns and discuss the strengths and weaknesses of each.

Pattern 1: Dedicated Loader Module

This is the lowest level of abstraction around the above hack. The idea is that you have a single module in your application that acts as a thin wrapper around the global require() and also provides a method to lazy load the ArcGIS API (i.e. programmatically inject a script tag on the page). It looks something like this:

Benefits

Helps enforce the best practice of pushing your dependencies (ArcGIS API for JavaScript), as well as the unsavory use of globals to the edge of your application

Only load the ArcGIS API for JavaScript on routes where it’s needed

Unit tests do not need to load the ArcGIS API for JavaScript; instead mock the loader service and any modules it would return

Challenges

application developers need to understand the asynchronous nature of loading ArcGIS modules and use appropriate techniques such callbacks or promises not only when loading map data, but also when loading ArcGIS API modules

you cannot use ES2015 import statements with this pattern

Examples

esri-loader is a standalone library that implements this pattern and makes it available to any Angular 2 or React application.

angular2-esri-loader exposes an Angular 2 service that wraps the esri-loader functions in ones that return promises to make it easier to use in Angular 2 applications.

esri-angular-cli-example is an example angular-cli application that uses the angular2-esri-loader library to lazy load modules from the ArcGIS API for JavaScript only on the map route. This application also includes example unit tests that demonstrate how to mock the loader and modules.

Similarly, esri-redux demonstrates how to use this pattern in an application built with React and Redux, and its predecessor, esri-flux-react, shows the same but with Flux instead of Redux.

The configurations from those application are used in create-react-app-arcgis which applies this pattern to the output of create-react-app.

esri-rollup-example is an example application that shows how to use this pattern with rollup.js instead of webpack.

FAQ

Which pattern is right for me?

If your application doesn’t have a map in every route, and/or is targeting mobile browsers, you probably want to consider using the dedicated loader module pattern. Also, this is the only pattern that currently works in angular-cli.

If you’re developing a map-centric application that primarily targets desktop browsers, the exclude and require pattern provides a clean abstraction that lets you import ArcGIS modules as you would modules from any other library.

What’s the catch?

Obviously relying on the global require() and define() functions isn’t exactly a best practice, and it’s the main source of risk in the above patterns. For example, because Ember.js defines these functions globally, the patterns demonstrated above won’t work in Ember.js applications. Thankfully ember-cli-amd provides an equivalent abstraction for working with ArcGIS API for JavaScript in Ember.js applications. So, as long as your application doesn’t use a library that relies on those globals, these patterns should work for you.

As I mention above, the dedicated loader pattern has been battle tested in at least a few production Angular 1 applications. Also, there’s a good discussion about (the apparent lack of) limitations of the exclude and require pattern in this issue thread.

It may be best to just think of the pre-built ArcGIS API for JavaScript as a special chunk that you can choose whether to load up front (with either pattern) or lazy load (with the “dedicated loader module” pattern).

A community effort

The ArcGIS developer community has come up with innovative solutions to the challenge of loading and consuming ArcGIS modules in Angular 2 and React applications built with webpack. In particular I’d like to thank @patrickarlt, @ScottONeal, @willisd2, @trkbrkr2000, @jwasilgeo, @Robert-W, @lobsteropteryx, @gund, @davetimmins, and @kgs916 for their contributions to the above solutions. I hope I’ve left you with a better understanding of how those solutions work and when you might want to use one or the other.

I also hope to inspire you to contribute to these projects and continue to evolve these solutions. There’s still plentyofworktobedone. Optimization techniques like tree shaking, code splitting, and lazy loading are still relatively new to most webpack developers. As successful patterns emerge, the ArcGIS developer community will have to figure out how to extend and/or emulate those patterns with the ArcGIS API for JavaScript.

Although my name is on a lot of those repositories, I’m going to play less of a role in this community effort going forward. For almost a year now I’ve been happily developing with Ember.js, which uses broccoli.js instead of webpack. To be honest, beyond the idiosyncrasies of loading ArcGIS AMD modules I only know enough webpack, Angular 2, and React to be dangerous. Now that this community has established some base module loading patterns, and since Ember.js is the framework I use day to day, I’ll be focussing my energies on making it easier for Ember.js developers to work with the ArcGIS platform.