Step 2 – Learn Webpack

As I stated, for this project I decided it was time to look at Webpack.  More and more of the web community is switching to Webpack.  It is bundled in Angular-CLI for Angular 2/4.  And though we don’t use Angular 2+ yet (Angular Material 2 is not ready for prime time and we use Angular Material with Angular 1.6+ extensively), I figured I better learn Webpack before we switch to it (probably later this year).

Simple Example

So for those who don’t know:

webpack is a module bundler. This means webpack takes modules with dependencies and emits static assets representing those modules.

What that means is that if you have a file, called app.js for example, and it needs another file, called service.js, you can declare that dependency, and have webpack produce a single javascript file that will load the files in the right order and satisfy the dependencies.  Let me show you by example.  Given the follow 2 files (app.js and service.js):

// app.js

import myService from ‘./service’;
myService();

// service.js

export default function myService() {
//
}

We can see that app.js has declared it’s dependency on service.js (the import statement).  And if we write a webpack config (which is called webpack.config.js by convention), like this:

module.exports = {
entry: ‘./app.js’,
output: {
filename: ‘bundle.js’
}
}

and run webpack (which in my case is done automatically for me by the WebPack Task Runner I installed in Visual Studio…), you will get a file called bundle.js that contains both app.js and service.js in the proper order (along with extra code to help resolve the dependencies, and, in the case of more complicated applications especially, make sure that a module is only loaded and initialized once).

The Parts of webpack.config.js

So for those that are new to Webpack, let me explain this file:

entry — This tells Webpack which file is the main entry point for your application (if you think in terms of C#, Java, etc, you could think of this file as your main method).  It will analyze the dependencies of this file to find all the other files it should bundle together.

output — Well, this tells Webpack what to output the resulting js bundle as.  In this example, it’s pretty straight forward, but you can get fancier (I did, that’s up next).

But this really doesn’t show the power of Webpack.  Let me show you the file I’m using in my project.

Simple, but Powerful Example

/// <binding ProjectOpened=’Watch – Development’ />

‘use strict’;
const UglifyJSPlugin = require(‘uglifyjs-webpack-plugin’);
const webpack = require(‘webpack’);
const path = require(‘path’);

const rootPath = ‘./Scripts/’;

module.exports = {
entry: {
app: ‘./app/index.ts’,
vendor: [‘angular’, ‘angular-material’, ‘angular-ui-router’, ‘angular-local-storage’,
‘o.js’]
},
output: { filename: path.join(rootPath, ‘app.js’) },
module: {
rules: [
{
test: /\.tsx?$/,
loader: ‘ts-loader’
},
{
test: /\.html$/,
loader: ‘html-loader’
},
{
test: /\.css$/,
loader: “style-loader!css-loader”
},
]
},
plugins: [
new UglifyJSPlugin(),
new webpack.optimize.CommonsChunkPlugin(
{
name: “vendor”,
filename: path.join(rootPath, ‘vendor.bundle.js’)
}
)
],
resolve: { extensions: [‘.ts’, ‘.tsx’, ‘.js’, ‘.css’]
}
};

Webpack Entry

So let’s talk about what this does.  First off, I use Typescript for everything JavaScript.  For those who don’t know what Typescript is, it is a way of making JavaScript amazing!!  It allows you to use classes, generics, and modules and have it turn it into JavaScript that will run in your browser (well, it does a lot more than that, but for the sake of this series, it’s why I use it).  The tooling in VS2015 is simply amazing.  It makes Typescript a full fledged language, complete with IntelliSense, Code Completion, and Go to Definition (you wouldn’t believe how much I missed this when I was doing only Javascript).

So my entry point for my JS app is actually a typescript file (index.ts).  But that’s not all that is going on here.  Thanks to the CommonsChunkPlugin, I can also specify other entry points (angular, angular-material, etc) that are resolved via NPM and can group them together (in this case, I grouped them as “vendor”).  When Webpack runs, the CommonsChunkPlugin will extract these entry points (and all related files) and place them in the specified file (in this case, vendor.bundle.js).  So the resulting output of running this webpack config is 2 files:  app.js (which contains my code) and vendor.bundle.js (which contains all third party code).

Webpack Rules

Next, we have the rules (which must be nested inside module).  Rules tell Webpack how to process different file types.  The first rule is the rule that tells Webpack how to deal with Typescript files.  The “test” parameter of a rule tells Webpack which files match this rule and it uses regular expressions to do so.  In this case, any file ending in .ts or .tsx will match the first rule.  The next part of a rule is the loader (a library that tells Webpack how to turn the matching file(s) into JavaScript), which in this case is ts-loader.  Loaders come from NPM / Yarn.  So to install ts-loader, you simply issue a command like:

npm install –dev ts-loader

So rest of the rules are:

html — With the html-loader, you can do some interesting things.  It will take the matching files and inline them into JavaScript.  so inside my Angular code, when I write something like this:

{ template: require(‘./templates/component.html’) }

the html-loader, takes the component.html file and inlines it into the resulting webpack bundle and during runtime, the html is passed as the template to angular.  which is really cool.

css — This example is interesting.  This rule tells Webpack to inline any referenced css file.  So inside my Typescript file, I write:

import ‘angular-material/angular-material.css’;

and Webpack will inline the css, but not only that, at runtime, it will inject the stylesheet into the HTML automatically for me (when the file that contained this import is called).  One thing of note here is the loader, as it is actually 2 loaders chained together.  For this trick to work (inlining the CSS and injecting it into the browser) requires that we do this. Chaining goes from right to left, so when we see style-loader!css-loader think of it like a method call, which would look like this:

styleLoader(cssLoader(“angular-material/angular-material.css’));

There is something really cool that happens that is not obvious from the webpack config, which I will explain by way of an example.  There is a plugin called file-loader, which will take the referenced file and output it beside the JS bundles, renamed as the hash of the file + the original extension.  When the html-loader and css-loader run to inline their respective files, they will use see references to PNG files and if we have registered file-loader as handling images, like so:

{ test: /\.png$/, loader: ‘file-loader’ }

It will alter the html so that:

<img src=”logo.png” />

becomes:

<img src=”12356abc45312356.png” />

and a file called 12356abc45312356.png will be outputted along with the app.js.  It will also do the same with CSS so that:

body { background: url(background.png); }

will not only be inlined into the app.js, it will come out as:

body { background: url(123454634532156.png); }

and background.png will be outputted as 123454634532156.png.  It gets even more interesting if you run the images through an image-loader that optimizes the images before writing them out.  That’s pretty powerful.

Webpack Plugins

Finally, we have the plugins.  Plugins basically run after all the loaders have converted the matching files into JavaScript, but before it is written to the bundles.  So the 2 plugins I have here are:

UglifyJSPlugin() — This plugin minifies / uglifies the JavaScript, which removes all comments and whitespace, simplifies private variable / function / etc names.  All with the goal of making the resulting javascript much smaller.

CommonsChunkPlugin — This plugin, as I said earlier, takes all the entry points and associated files that are flagged as the given name and extracts them from the main bundle and outputs them as a secondary bundle.

Binding Comment

You may notice at the top of the example there is:

<binding ProjectOpened=’Watch – Development’ />

This simply tells Visual Studio (when the Webpack Task Runner plugin is installed…) to run the “Watch – Development” Webpack task when the project is opened (Which starts WebPack in watch mode and recompiles the JS bundles whenever the files in the bundle change).  So without me having to remember to start it, when the project is opened, bundles will be automatically updated as I make changes.  Got to love that 🙂

And with Web Essentials 2015 installed, when the bundles are updated, the web browser refreshes automatically as well.

Advertisements

2 thoughts on “Step 2 – Learn Webpack

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s