Last Updated: 2/24/2016
After watching Pete Hunt’s OSCON 2014 video on how Instagram.com was built, I felt inspired to learn more about Webpack. Up to this point, I had been using RequireJS with Grunt. While this duo has worked quite well, flexibility was lacking in terms of module support. Being tied to AMD was less than ideal, especially as I began to see more and more value in using CommonJS modules. Another motivation was to part ways with r.js, the optimization tool bundled with RequireJS. Sifting through r.js’ seemingly mile-long list of options was daunting. Needless to say, arriving at a successful configuration was arduous and nothing short of dumb luck.
My assumption is that you’ve already installed Node.js with npm. If not, head over to nodejs.org and do so now. Node has become my crutch for modern web development, and I often wonder how I produced anything of reasonable quality without it. If you haven’t taken the time to learn it, do yourself that favor now. Pluralsight is a great starting point.
Now that my Node plug is out-of-the-way, let’s install Webpack. The CLI is installed globally via an npm package:
npm install –g webpack
Next, create a new project which mimics the file/directory structure depicted in Figure A. There’s much debate over the best way to structure an Angular 1.x application. If you’re looking for some direction, take a look at John Papa’s style guide. On the other hand, if you have a preferred way of structuring things, stick with it. Before proceeding, note the file name containing your main Angular application module. That file name will be referenced in the next step.
This code snippet sets a pointer to the app directory, instructing Webpack where to start looking. It’s the context option’s absolute path which aids in resolving the relative path located in the entry option. The app.module.js file is where Webpack will begin its processing, and the output will be sent to a bundle.js file within the dist directory. As your application grows, it’s common to have multiple entry points. To support that scenario, the entry option can accept an object literal. See the documentation for an example.
Another powerful feature Webpack supports is the creation of path aliases. The thought of typing node_modules over and over again makes me cringe. Consequently, I’ve setup an alias for it called npm, which maps to the absolute path of the node_modules directory. From this point forward, importing npm modules requires fewer keystrokes.
Note that the creation of the aforementioned npm alias is completely optional. I prefer to be explicit with my module import / require statements, so I choose to include the npm alias to eliminate ambiguity among developers who are less familiar with Webpack.
What about ES 2015 support?
For the Webpack configuration file
By renaming the webpack.config.js file to webpack.config.babel.js, one can make use of ES 2015 features within the Webpack configuration file. This, of course, assumes that the Babel npm module is installed locally in this project. Install the aforementioned module as follows:
npm install --save-dev babel
The Webpack configuration file which was provided above could now look like the file below. Note the use of template strings, along with the new let and const keywords.
For the application files
Since we want support only for the finalized ES 2015 specification features, the Babel ES 2015 preset is used here (see babel-preset-es2015 on npm).
There’s also a jshint-loader npm package being used here. Although, note the presence of this particular loader in the preLoaders array. Preloaders execute before loaders. This makes sense only because we want to perform static code analysis on the ES 2015 code before it’s transpiled to ES5 by Babel. JSHint will search for a .jshintrc file at the project root and then perform its static code analysis based on the rule set defined in the aforementioned file.
Execute the following command to install the two loaders described above:
npm install --save-dev babel-loader jshint-loader
What’s with the other loaders and plugins?
Though not related to ES 2015 and Angular, the webpack.config.js file leverages other extensibility points for handling Bootstrap CSS and fonts. Since Webpack treats virtually every asset type as a module, loaders are used to parse and to inject the CSS and fonts into the DOM. Install the prerequisites as follows:
npm install --save-dev css-loader file-loader style-loader url-loader
When Webpack parses the modules, it evaluates the import and require statements in app.module.js:
Additionally, there’s a need to clean the generated dist directory on each run of the build. A clean-webpack-plugin npm package is used for this purpose. Also present is a plugin for safely annotating Angular module dependencies. This particular plugin will act on any occurrences of the
/* @ngInject */ comment. Install the requisite packages as follows:
npm install --save-dev clean-webpack-plugin ng-annotate-webpack-plugin
How to Run Webpack
There are a couple other useful CLI options which will increase the verbosity of the CLI’s output:
--progress. As one might guess,
--progress will display the compilation progress as a percentage. This is useful for complex, long-running builds. The
--display-modules option will expand what would otherwise be a simple module count into a detailed listing of those modules included in the bundle.
As you’re beginning to see, the CLI commands can be quite lengthy. Wouldn’t it be nice if we could abstract away the intricacies of running Webpack each time? Open your package.json file, and locate the scripts section. Within there, add the following 2 scripts:
"dev-build": "webpack -d --display-modules --progress"
"dist-build": "webpack -p --display-modules --progress"
Let’s assume I’m preparing a production build of the application. In that case, use this terse command:
npm run dist-build
The CLI output can be found in Figure B.