Bundling AngularJS without editing thousands of files – Journey to webpack – part 7

January 27, 2018 | By Brian | Filed in: work.

New to the journey? Start from the beginning

Now that we’ve gotten our 2nd and 3rd party libraries, including momentjs bundled with webpack, it’s time to move onto our main application code.

As I’ve mentioned, we’ve amassed a fairly large AngularJS (1.x ) application without a module system like CommonJS or AMD, so the options for how to best bundle them with webpack are limited. Currently we use Grunt to build up separate modules for each feature, which allows us to only download the code for features that the user has permission.

The thought of modifying 1000s of JS files didn’t sit well with anyone, so we didn’t even start down that path. Now, if you have a code base that is fairly small, this might be the quickest path forward. AngularJS (1.x), unlike Angular (2+), doesn’t have any real concept of an “entry point” which is an important piece to the webpack config. Basically, you point webpack at your entry point, and it chases down the dependencies by following any require or import statements it finds along the way.

With that in mind, my teammates came up with the following method to “properly” require all necessary files to make webpack happy:

# ./entry-points/feature1.ts
export const importAll = (r: any): void => {
  r.keys().forEach(r);
};
importAll(require.context('./app/feature1', true, /module\.js$/));
importAll(require.context('./app/feature1', true, /(^(?!.*(spec|module)\.js).*\.js)$/));
directory layout

Directory layout

With this little snippet, we’re able to require every file in the ./app/feature1 directory, ignore any spec files or spec-helpers, and very importantly, referencing the module.js first. module.js is our teams standardized filename for defining any AngularJS modules.

So our webpack config can point to this file:

entry: {
  'feature1': './entry-points/feature1.ts'
}

And generate a feature1.js output file that contains all of the non-test code! So, we repeated this pattern for about 15 feature directories, and were able to generate the same bundles that we manually had put together with grunt tasks and globs.

One bonus we found as we put this together was that any code that had been converted to TypeScript didn’t need to be part of this entry file. The way that we write our TS files, we always create a barrel that contains imports of any TypeScript modules, classes, constants, etc. So, for anything that was already converted to TypeScript, we just needed to use the barrel as the entry point  to let webpack chase the dependencies. Of course we have many features that are part JS and part TS, in that case the entry point looks like this:

entry: {
  'feature1': ['./entry-points/feature1.ts', './app/feature1/ts/index.ts']
}

Where the first element of the array contains the JS parts of the feature, and the second points at the top level barrel for the TS parts of the feature!

feature1.ts (module barrel)

./app/feature1/ts/index.ts (module barrel)

Shout out to my teammates Tor, Pramod & Greg for supplying the brain power for this!

 

A quick update to our TODO list:

  1. Create vendor.min.js
  2. Create app-components.min.js
  3. Create app.min.js
  4. Create templates.app.min.js
  5. Try out webpack 3.0!
  6. Figure out what’s up with moment.js
  7. Use the webpack’d files in karma for unit tests
  8. Try the CommonChunksPlugin
  9. Handle CSS/LESS/SASS
  10. Use the dev-server instead of grunt-contrib-connect

 


Tags: ,

Leave a Reply

Your email address will not be published. Required fields are marked *