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:

  1. Exclude ArcGIS API modules from any bundling done by other module loaders
  2. Load the ArcGIS API for JavaScript via a script tag on the page
  3. Use Dojo’s global require() and define() functions to load ArcGIS modules

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:

// import the loader module
import * as esriLoader from 'esri-loader';
// then use Dojo's loader to require the map class
esriLoader.dojoRequire(['esri/map'], (Map) => {
  // create map with the given options at a DOM node w/ id 'mapNode'
  let map = new Map('mapNode', {
    center: [-118, 34.5],
    zoom: 8,
    basemap: 'dark-gray'
  });
});

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.

esri-react-router-example is an example react-router application that uses the esri-loader library to lazy load modules from the ArcGIS API for JavaScript only on the map route.

create-react-app-esri-loader applies this pattern to the output of create-react-app.

This pattern finds it’s roots in angular-esri-map, a library for using ArcGIS maps and services in Angular 1 applications.

Pattern 2: Exclude and Require

This pattern provides a slightly higher level of abstraction around the above hack. The way it works has been well documented in this gist, and here are the key parts:

  1. Configure webpack to exclude the Dojo modules from the ArcGIS API for JavaScript
  2. Configure webpack to output the bundles themselves as AMD (or UMD) modules
  3. Load the ArcGIS API for JavaScript via a script tag
  4. Use the Dojo loader included in the ArcGIS API for JavaScript to require() the bundles

Then in your application you can do something like:

import Map from 'esri/Map'
let map = new Map('mapNode', {
  center: [-118, 34.5],
  zoom: 8,
  basemap: 'dark-gray'
});

Benefits

  • Allows you to use ES2015 import statements
  • No need for asynchronous code (callbacks or Promises) to load ArcGIS modules

Challenges

  • The (rather hefty) ArcGIS API for JavaScript will have to finish loading before the rest of your application can begin loading
  • Using ES2015 import statements confuses some developers who don’t understand where those modules are coming from
  • Existing unit test examples load the ArcGIS API for JavaScript in the test harness instead of mocking them
  • No one has figured out how to use it with angular-cli yet

Examples

At the most basic level, this pattern is exemplified in the esri-webpack-typescript boilerplate. esri-webpack-babel is a fork of that boilerplate that uses Babel instead of TypeScript and includes inline comments and documentation on how it works.

angular2-esri-example demonstrates how to use this pattern in an Angular 2 application.

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.

Will we need these patterns once dojo-webpack-loader works?

I think that it won’t be long before someone resolves the issue with dojo-webpack-loader not being able to load ArcGIS modules. That said, I think the above patterns may still prove useful. There are several other unresolved issues that indicate that it’s implementation of Dojo plugins is not complete. Furthermore, I question whether webpack can do a better job of building the ArcGIS API for JavaScript than Dojo does. More importantly, how will webpack handle the files that the are traditionally excluded from Dojo builds of the ArcGIS API and then required asynchronously at runtime? What about the CSS?

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).

Do I need to use the CDN?

No. While all of the above examples for each of the patterns use the CDN, you should be able to use any of the other ways to get the ArcGIS API for JavaScript such as a locally hosted Bower build or SDK download.

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 plenty of work to be done. 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.