jpeterson-esristaff

How to Tame your Web App - Module Bundling 101

Blog Post created by jpeterson-esristaff Employee on Mar 7, 2017
First, the links:

Crowdsource Reporter Repo: https://github.com/esri/crowdsource-reporter

My Migration Repo: https://github.com/jpeterson/crowdsource-reporter

 

Where we are

This is the third part in a seven-part blog series called How to Tame your Web App. Here’s where we are:

  1. The Plan
  2. ES6 and Modern Modules
  3. Module Bundling 101 (you are here)
  4. Module Bundling 201 - Optimization
  5. Linting
  6. UX and DX: The Best of Both Worlds
  7. Legacy Support - coming soon

 

Today's place: Colorado

Header: San Juan Mountains above Ouray, Colorado.

 

Mt. Sneffels

Enjoying the view of Mount Sneffels one of Colorado's 53 fourteeners.

 

Module Bundling 101 

 

The Plan

Implement a module bundler (we’ll use Webpack 2 here) to turn our beautifully architected and readable code into a slim, optimized package for our users to consume.

 

The Reason

Modules are an amazing tool for architecting applications, but the way the web works today, loading a bunch of files in a web app is detrimental to the performance of the app. A module bundler will allow us to maintain our great developer experience while still providing the most optimized user experience possible by building, or bundling, our app for production.

 

The Commits

https://github.com/jpeterson/crowdsource-reporter/pull/3/commits

 

Before we get started, I’ll admit that this is by far the most involved step. Webpack is designed to be modular and leverage the work of others where possible – this makes it powerful, but can also make the setup process seem confusing. If you’ve worked with the Dojo Build system before, you know build/bundle systems are inherently complex beasts. If you get stuck, ask for help!

 

First off, we’ll be installing some npm modules locally with our project, so we need a package.json. Let's scaffold out a package.json with a simple command in the project root:

> npm init

 

Next, install Webpack:

> npm install webpack --save-dev

 

Webpack processes our assets with the help of loaders. These are simply npm packages that handle specific responsibilities within the webpack bundling process. For now, we will install loaders to handle HTML and CSS files (Webpack can handle JS on its own for now):

> npm install html-loader style-loader css-loader --save-dev

 

Next, we’ll create a configuration file for Webpack. If you’ve used Grunt or Gulp before, this is very similar to a Gruntfile.js or Gulpfile.js. The default name for a Webpack config file is webpack.config.js. Create this file in the project root and copy the contents of this file into yours. You can keep everything the same except for the entry property – this will need to point to the entry point of your app.

 

The next step is going to be a little more specific to the structure of your app. We need to modify our index.html and entry file to be more JS-centric (i.e. don't invoke your JS from index.html). In the case of the Crowdsource Reporter, here was the situation:

 

First, here’s my commit diff for reference. And a photo.

 

Dallas Peak

Dallas Peak, one of Colorado's 637 thirteeners.

 

Here’s the scenario in the existing app: index.html contained a require() which imported bootstrapper.js (the entry file in this app). It then instantiated a new instance of that module. Finally, it called the startup() method on bootstrapper.js.

 

Instead, we need index.html to simply require our bundle.js (the output from Webpack). We then need our entry file to invoke itself. In this case, I named the Dojo module (Bootstrapper) and invoked the constructor and startup methods within bootstrapper.js.

 

While your setup may be slightly different, the basic idea is that you need to kick off your app from within the JS file you define as your entry point – not from index.html.

 

At this point, we might be done. However, you’re likely using the Dojo Loader’s dojo/text! plugin, which Webpack isn’t aware of. This is why we installed Webpack’s html-loader earlier – it does almost exactly the same thing as the Dojo Loader plugin. One beautiful thing about Webpack loaders is that when we configure them properly in webpack.config.js, we don’t need to reference them in our JS, so it’s a simple matter of finding and replacing “dojo/text!” with “” to get what we need.

 

To avoid overcomplicating things, I'll address the externals section of the Webpack config by saying that it tells Webpack to ignore all modules beginning with dojo, dojox, dijit, and esri in the bundling process, and assume that they will be available for loading at runtime. If you want to know more about this - read the following tangential section...

 

<-- Begin Tangent -->

 

Okay – in some ways, this is the crucible of this entire exercise. Getting Webpack to play nicely with the Dojo Loader. The problem this section of the configuration file addresses is that the Dojo Loader does not implement the AMD standard like most module loaders (including Webpack), so lots of issues arise when attempting to run Dojo and JSAPI modules through the Webpack (or any other module loader’s) bundling process.

 

Luckily, a lot of very smart people in the ArcGIS developer community have come up with some creative ways to work around the Dojo Loader. The most succinct illustration of this problem and the various solutions comes from Tom Wayson’s blog post: Using the ArcGIS API for JavaScript in Applications built with webpack. If you’re interested in the details of the problem and gaining a deeper understanding of the solutions and workarounds – start there, and make sure to click all the links.

 

A few shoutouts to those smart people, in case you don't dive into that blog post... 

tomwayson (Tom Wayson) · GitHub 

lobsteropteryx (Ian Firkin) · GitHub 

gund (Alex Malkevich) · GitHub 

Robert-W (Robert) · GitHub 

 

You stuck with me through all that? Here's a photo.

 

Denver, Colorado

The beautiful mile-high city of Denver, Colorado. As seen from my roof.

 

<!-- End Tangent -->

 

To get our app back to functional, we could simply run webpack from the command line. Instead though, let’s add it as a script to our package.json so we don’t have to remember any command line arguments later. For now, we’ll simply run webpack with no arguments by adding the following property to the “scripts” section of package.json:

"scripts": {    
  "build": "webpack"
}

 

The moment of truth! In your terminal, try running:

> npm run build

 

If you’re fist pumping at your awesome app right now, you had more luck than I did! You deserve a photo.

 

Waterfall   Waterfalls are awesome. Waterfalls in the Uncompahgre National Forest are the awesomest.

 

I ran into a few issues here. You may or may not have run into them as well, but I’m going to list them out (along with how I resolved them) just in case.

 

First, I missed a few modules when manually converting package names to relative paths. The webpack build nicely alerted me, so I just had to go fix them.

 

The second issue took much longer to figure out. Webpack successfully gave me a bundle, so that was positive. But when I opened my app in the browser, I saw this in the console: 

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

 

This error is complaining about things related to strict mode. I wasn’t even aware my code was being run in strict mode, so to Google I went. I found out that the ES6 Module spec requires code to be in strict mode – who knew? Given the fact that this is in spec, Webpack naturally adds use strict to any ES6 modules it finds (i.e. all our code).

 

I guessed that shouldn’t be a big problem though – strict mode is a good idea anyway, right? Well, apparently, Dojo doesn’t think so. The error I was seeing can be traced to the numerous places in the app where Dojo modules do inheritance the Dojo way, using this.inherited(arguments). It’s a known fact that Dojo’s inheritance model is straight-up incompatible with strict mode.

 

I have an admission of guilt: I cheated to fix this problem. I followed a hunch that the original developers had added this.inherited(arguments) more as a matter of practice than actual necessity. Therefore, I simply commented out all occurrences of that statement, and the app worked. I admit, this is a total cop out! But it is also a possible solution – if your app doesn’t need this type of inheritance, simply remove it.

 

Luckily, I did stumble across an alternative workaround using Babel. If you want/need to do this, jump to the Legacy Support section below to add Babel to your Webpack config, and include the additional preset es2015-without-strict.

 

That was it for me – after resolving those issues, my app was back to a fully functioning state. Step back and take some joy from what you have so far. Even if you stop here, you have:

  • Moved away from AMD to JavaScript Modules
  • Implemented some ES6 in your codebase (arrow functions, let/const, template strings, object shorthand)
  • foundational webpack setup, with most of your code being bundled into a single file.

 

I’d recommend taking account of your app here. Are there other ES6 features you could take advantage of?

  • Revisit our earlier discussion of those Dojo language functions – would you rather use idiomatic JavaScript?
  • Peruse the remaining Lebab transforms – would any of them improve your codebase?
  • If you aren’t using Dojo’s class system much, adopting JavaScript Classes might be an option

 

Once you’re happy with things, let’s move on. With this foundation, we can start unlocking some of the bonus features of modern web development!

 

Next up is part 4 of this series, Module Bundling 201 - Optimization.

Outcomes