Angular Build with Webpack from Scratch – Part 2

Angular Build with Webpack Hero

Derived from photo by Yann Caradec / flickr.com, CC BY-SA

This is the second post in a two-part series getting started with webpack from scratch. The first part covered how to configure the Angular-specific portion of a webpack build. This post walks through how to configure the HTML, CSS, and development server. It builds upon the configuration started in part one.

If you want to jump straight to the code, the completed demo is on GitHub.

Webpack Context

As webpack begins to handle more of the application, it becomes necessary to configure multiple entry points for the build. Part one configures the full path for the single entry file. You could follow the same pattern with any new entry files. You could instead set the context configuration property.

The context property sets a directory as the location where file paths resolve. Instead of combining the paths of each entry file, you configure the base path and then specify the individual file names. This is how part one configured the single entry for webpack:

const path = require('path');
const source = path.resolve(__dirname, 'src', 'index.ts');

module.exports = {
    entry: source,
    // ...
};

To prepare for multiple entries, refactor the entry to use the context property as well:

const path = require('path');
const source = path.resolve(__dirname, 'src');

module.exports = {
    context: source,
    entry: [
        './index.ts'
    ],
    // ...
};

Note: The __dirname variable is a string from Node.js providing the current module's directory.

Using the context property is optional but it helps to remove repetitive path combining logic from your configuration.

HTML Build

Single-page applications are named this way because they typically contain one HTML document that loads the scripts to bootstrap the application. Angular is no exception and there is a handy plugin for webpack called HtmlWebpackPlugin to dynamically configure the HTML page.

The initial configuration for HtmlWebpackPlugin is straightforward. Import the module from its npm package, html-webpack-plugin, and then add it to the plugins property of the webpack configuration.

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // ...
    plugins: [
        new HtmlWebpackPlugin()
    ]
};

By default, the plug-in gives you a boilerplate HTML file for your application. You can however, supply it with a template through the module's constructor arguments. Keep in mind that when using a template, the HtmlWebpackPlugin automatically adds a script element referencing the output of the webpack build so ensure you haven't added the script reference multiple times. This is how to specify the template for HtmlWebpackPlugin:

module.exports = {
    // ...
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html'
        })
    ]
};

Now you have a webpack build that includes the JavaScript and HTML for your application where the bundle is automatically referenced in the output index.html file. Try it by running npm run build at the command line and inspect the output in the dist directory. The HtmlWebpackPlugin has many great extensions. You can find some of them listed in the project's README file.

CSS Build

The final portion of the Tour of Heroes to address is the global CSS. In the first part of this series, you configured webpack to load any CSS files that are referenced by Angular components. However, the application has global CSS defined in the styles.css file. This file includes style rules relevant to the overall look and feel of the application so it makes sense to keep this outside of the individual components` CSS.

The configuration already processes CSS for the components but this configuration won't work for the global CSS. The first thing is to define the boundary between when webpack should process CSS for components and when webpack should process the global CSS. Looking at the application, everything in the directory .\src\app\ needs to be configured for the angular2-template-loader where other CSS in the .\src\ directory should be bundled and included as a linkelement in the index.html file.

Define a variable for the .\src\app\ path and use the include property on the raw-loader configuration:

const source = path.resolve(__dirname, 'src');
const appDirectory = path.resolve(source, 'app');

module.exports = {
    // ...
    module: {
        rules: [
            // ...
            {
                test: /\.(css|html)$/, 
                include: appDirectory,
                loader: 'raw-loader'
            },
            // ...
        ]
    },
    // ...
};

To process the non-component CSS, use the ExtractTextPlugin. This plugin is essentially a wrapper that provides bundling functionality. It leverages other loaders' import logic to load the CSS content. In the end, you get a combined CSS file. Import the plugin from its npm package, extract-text-webpack-plugin. You also use the style-loader and css-loader npm packages for their respective CSS loaders.

Add the following to the rules array of the webpack configuration:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

const source = path.resolve(__dirname, 'src');
const appDirectory = path.resolve(source, 'app');

module.exports = {
    // ...
    module: {
        rules: [
            // ...
            {
                test: /\.css$/,
                exclude: appDirectory,
                loader: ExtractTextPlugin.extract({ 
                    fallback: 'style-loader', 
                    use: 'css-loader?sourceMap' 
                })
            },
            // ...
        ]
    },
    // ...
};

You see this configuration loads files ending in the .css extension and excludes the appDirectory CSS. Remember, the appDirectory path contains your Angular code and undergoes a different build process than global styles.

The ExtractTextPlugin is configured as both a loader and a plug-in. To configure the plug-in portion of ExtractTextPlugin, add it to the plugins array. Provide a name for the final output bundle, for example styles.css.

module.exports = {
    // ...
    plugins: [
        // ...
        new ExtractTextPlugin('styles.css')
    ]    
};

Note: This tutorial uses styles.css as both the name of the input file and the output file. Feel free to customize this to help ensure you understand which piece of the configuration is related to inputs and which is related to outputs.

Finally, webpack needs to know about the existing Tour of Heroes styles.css file so add it as an entry in the configuration:

module.exports = {
    context: source,
    entry: [
        './index.ts',
        './styles.css'
    ],
    // ...
);

Now that the CSS is auto-generated, the HtmlWebpackPlugin and ExtractTextPlugin work together to add a link to the index.html template file. Remove any existing links to styles.css from the index.html file.

The ExtractTextPlugin has several different options and uses so it's worth taking a look at its documentation.

At this point, your webpack build is complete. Try it with npm run build to see the output. The final HTML template and webpack configuration should have the following contents:

<html>
<head>
  <base href="/">
  <title>Angular 2 Tour of Heroes</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <my-app>Loading...</my-app>
</body>

</html>
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const source = path.resolve(__dirname, 'src');
const destination = path.resolve(__dirname, 'dist');
const appDirectory = path.resolve(source, 'app');

module.exports = {
    context: source,
    entry: [
        './index.ts',
        './styles.css'
    ],
    output: {
        filename: 'index.js',
        path: destination
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loaders: [
                    'awesome-typescript-loader',
                    'angular2-template-loader'
                ]
            }, 
            {
                test: /\.ts$/,
                loader: 'webpack-replace',
                query: {
                    search: 'moduleId: module.id,',
                    replace: ''
                }
            },
            {
                test: /\.css$/,
                exclude: appDirectory,
                loader: ExtractTextPlugin.extract({ 
                    fallback: 'style-loader', 
                    use: 'css-loader?sourceMap' 
                })
            },
            {
                test: /\.(css|html)$/, 
                include: appDirectory,
                loader: 'raw-loader'
            },
        ]
    },
    plugins: [        
        new HtmlWebpackPlugin({
            template: './index.html'
        }),
        new ExtractTextPlugin('styles.css')
    ]
};

Development Server

The webpack development server integrates with webpack to provide many great features to aid your development. This functionality is installed with the webpack-dev-server npm package. The server automatically watches for changed files and refreshes the browser once a new compilation is complete. To improve performance, the webpack development server keeps the generated contents in memory as opposed to writing it to disk.

To setup a project for the webpack development server, create an npm script. For this tutorial, use the npm start command. There are several command line options available to configure the development server. Use the --open option to have the development server automatically open the default browser to the server's port on localhost:

"scripts": {
    "start": "webpack-dev-server --open",
    ...
}

While the development server has many configuration options, this tutorial covers two that are relevant to Angular developers: devtool and historyApiFallback. The devtool property specifies a source map type to aid in debugging at runtime. Go ahead and set the property to 'source-map'.

Then under the devTool configuration property, set the historyApiFallback property to true. This property tells the server to always return the index.html contents when a route is not available on the server. This is very useful when using client-side routing so that the server defers routing responsibility to Angular's router. Define these properties in your webpack configuration:

// ...
module.exports = {
    // ...
    devtool: 'source-map',
    devServer: {
        historyApiFallback: true,
    }
};

Save your configuration and type npm start at the command line to see webpack build and serve the application.

Final Thoughts

Now you have a starting point for your webpack build and more importantly, you understand how these pieces work together. Many of the more advanced webpack examples you see online use these pieces as their foundation so you can more easily add additional features to the build as you need them.

If you want to see this build in action, look at the repository on GitHub.

What are the next steps on your webpack journey? Which aspects of the build are still causing issues for you? Please sound off in the comments.

Angular Build with Webpack from Scratch

Angular Build with Webpack Hero

Derived from photo by Yann Caradec / flickr.com, CC BY-SA

This tutorial walks through creating a webpack-based build for the Angular Tour of Heroes sample application. By the end, you will have a basic build and development server with automatic refresh and source maps.

The tutorial is divided into two parts. In this first part, you setup the project and create the Angular-specific build. In part two, you process the other files of the application and setup the development server. Read part two here.

More importantly, you will understand what each piece of the webpack configuration does. Why is it important? How does it relate to other pieces of the build? This understanding is critical when you inevitably want to modify or add new webpack functionality.

Migrate Tour of Heroes

First, create a new directory for your project. Before copying any of the Tour of Heroes code into your repository, create a folder named src in the root of your project. This folder will house the source code related to the browser-based Angular web application. In contrast, the project's root directory is the place for configuration files for the build and overall project. These specific directory names and locations aren't required but consider partitioning your project's source code according to the application's concerns, e.g. client versus configuration.

Tip: Clone the Angular Tour of Heroes sample application locally to copy files easily.

Create the src directory within your project and copy the entire app directory, index.html, and styles.css files from Angular Tour of Heroes into the newly created src directory. Then, copy the tsconfig.json and package.json files into the project's root directory.

Your application structure should look like this:

├── src
|   ├── app
|   |   └── (...)
|   ├── index.html
|   └── styles.css
├── package.json
└── tsconfig.json

Now, make a couple modifications to the copied content. First, delete the src\app\main-aot.ts file. Ahead-of-Time (AOT) compilation is outside the scope of this tutorial and keeping this file causes a TypeScript compilation error.

Next, modify the packages in the package.json file. Since the entire build system is changing, replace the package list under devDependencies with this list:

"devDependencies": {
    "@types/node": "^6.0.45",
    "angular2-template-loader": "^0.6.0",
    "awesome-typescript-loader": "^3.0.3",
    "css-loader": "^0.26.1",
    "extract-text-webpack-plugin": "^2.0.0-beta.5",
    "html-loader": "^0.4.3",
    "html-webpack-plugin": "^2.16.1",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.5.2",
    "style-loader": "^0.13.1",
    "typescript": "~2.0.10",
    "webpack": "2.2.0",
    "webpack-dev-server": "2.2.0-rc.0",
    "webpack-replace": "^1.0.0"
}

For now, don't worry about these packages as they are noted throughout the relevant sections of the tutorial.

Now that the list of packages is updated, run npm install from the terminal to install the required packages.

Finally, replace the existing npm scripts with a single script to invoke the webpack build:

"scripts": {    
    "build": "rimraf dist && webpack"
}

Note: If you attempt to run this script from a tasks.json file in Visual Studio Code, webpack might throw an error. To work around, run the npm script from the terminal instead.

This script calls the npm package utility rimraf to clear the contents of the dist directory and then calls webpack. The dist directory will be where webpack saves the build output.

Going forward, use the build script by invoking the command npm run build in the terminal to create the build. As you iterate your build configuration, use this command to verify the output.

TypeScript Build

In most cases, an Angular application consists of TypeScript files using ES2015 modules with HTML templates and styles defined either in line with components or in external files. The webpack build needs to understand how to process this structure and bundle it into a format the browser understands.

The Angular framework requires a couple polyfill scripts from core-js and zone.js. In Tour of Heroes, these scripts are included in the index.html file prior to loading the application. Being that the bundling process uses ES2015 import statements to create the bundle, move these script references to a new file called index.ts. Later, when processing the index.html file, the build will reference this new script. Place the index.ts file in the src directory with the rest of the Angular application and add this code:

// Polyfills
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

// App
import './app/main.ts';

In addition to the polyfills, this file also imports the main.ts script to bootstrap the Angular application.

Now, it's time to create the webpack configuration. By default, webpack looks for a file at the root of the working directory named webpack.config.js. Go ahead and create this file and add the following contents:

const path = require('path');

const source = path.resolve(__dirname, 'src', 'index.ts');
const destination = path.resolve(__dirname, 'dist');

module.exports = {
    entry: source,
    output: {
        filename: 'index.js',
        path: destination
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loaders: [
                    'awesome-typescript-loader'
                ]
            }
        ]
    }
};

At a high level, this file contains three variables: path, source, and destination. The path variable represents an imported npm module to assist in concatenating the required file and directory paths represented by source and destination.

The configuration also defines an object literal as the module's exports. The object literal is the webpack configuration and has the following properties: entry, output, resolve, and module. The entry property points to the file where webpack begins to construct the dependency tree, in this case, the index.ts file you created earlier. The output property defines the output file's name and path, in this case, dist\index.js.

The resolve property assists loading the TypeScript files. Because the import statements don't typically use a file extension, webpack uses the extensions defined in resolve.extensions to reconcile the imports. The compilation of the TypeScript files is ultimately handled by the awesome-typescript-loader. This loader is included in the project as an npm package defined in the package.json file.

The module.rules property is an array of objects that represent which tools load which files. In webpack, there is a concept of a loader. Loaders are an extension point which customize how code is processed by the webpack build. The defined loaders array is used only when an import matches the regular expression supplied in the test property.

The awesome-typescript-loader compiles TypeScript into JavaScript. The loader is used only for files that match the regular expression /\.ts$/ as defined in the test property.

With this configuration defined, test your build by typing npm run build in the terminal. Look at the newly created dist folder to see the result – one single JavaScript file index.js. Examine the contents and you will start to get a sense of how webpack packages the application dependencies.

Angular Component HTML and CSS

Compiling TypeScript to JavaScript is the first step in an Angular build. Now, you must include the contents of any external templates and styles. Components define these resources in the @Component decorator:

@Component({
    templateUrl: 'my-template.html',
    styleUrls: [ 'my-styles.css' ]
})
export class MyComponent { }

The angular2-template-loader is listed under defDependencies in the package.json file. It extends webpack to load these external files and place the contents in line with the component. The resulting code looks something like this:

@Component({
    template: '<h1>Hello World!</h1>',
    styles: ['h1 { color: blue }']
})
export class MyComponent { }

The angular2-template-loader requires that webpack understands how to load HTML and CSS files. Therefore, add another rule to the rules list to test for .css and .html files and use the raw-loader package. The raw-loader is a simple loader that emits the contents of a file as a string.

This is how the new rules configuration looks with these changes:

rules: [
    {
        test: /\.ts$/,
        loaders: [
            'awesome-typescript-loader',
            'angular2-template-loader'
        ]
    }, 
    {
        test: /\.(css|html)$/, 
        loader: 'raw-loader'
    },
]

Run the build again to test that the new configuration works. To verify that the HTML templates are included, perform a search in the emitted index.js file for a piece of code from one of the external templates such as {{hero.name}}.

The moduleId Problem

The Tour of Heroes application is designed to use SystemJS. Instead of combining the ES2015 modules into one bundle on the server, SystemJS dynamically loads all of the modules it needs when the application runs in the browser.

To assist the dynamic loading process, developers oftentimes configure the moduleId property like this:

@Component({
    moduleId: module.id,
    templateUrl: 'my-template.html',
    styleUrls: [ 'my-styles.css' ]
})
export class MyComponent { }

The value passed by module.id is populated at runtime by SystemJS. Since SystemJS is not there anymore to populate the property, it is not defined and Angular doesn't like this. It throws the error 'moduleId should be a string in "AppComponent".

One way to get around this is to manually change all of the files by removing the moduleId definitions. After all, who needs all of this boilerplate code anyway? However, you may not always have access to this code. You may be using code authored by a third party which hasn't in lined the templates as part of a build. They have moduleId defined so that consumers can use SystemJS if they choose.

For this tutorial, instead of manually removing the moduleId settings, webpack will do it for you. Use the webpack-replace loader (another npm package) to search for the moduleId settings and remove them.

The webpack rules configuration now looks like this:

rules: [
    {
        test: /\.ts$/,
        loaders: [
            'awesome-typescript-loader',
            'angular2-template-loader'
        ]
    }, 
    {
        test: /\.ts$/,
        loader: 'webpack-replace',
        query: {
            search: 'moduleId: module.id,',
            replace: ''
        }
    },
    {
        test: /\.(css|html)$/, 
        loader: 'raw-loader'
    }
]

Run your build again using npm run build and search in the output for moduleId: module – the search should return no results.

Finally, the webpack.config.js file looks like this:

const path = require('path');

const source = path.resolve(__dirname, 'src', 'index.ts');
const destination = path.resolve(__dirname, 'dist');

module.exports = {
    entry: source,
    output: {
        filename: 'index.js',
        path: destination
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loaders: [
                    'awesome-typescript-loader',
                    'angular2-template-loader'
                ]
            }, 
            {
                test: /\.ts$/,
                loader: 'webpack-replace',
                query: {
                    search: 'moduleId: module.id,',
                    replace: ''
                }
            },
            {
                test: /\.(css|html)$/, 
                loader: 'raw-loader'
            }
        ]
    }
};

The working directory structure now looks like this:

├── dist
├── node_modules
├── src
|   ├── app
|   |   └── (...)
|   ├── index.html
|   ├── index.ts
|   └── styles.css
├── package.json
├── tsconfig.json
└── webpack.config.js

That's All for Now

This tutorial has taken you through the basics of building the Angular portion of the application. However, there are more pieces of the application that require processing. In part two, you handle the index.html and CSS portions of the application. In addition, you learn how to configure the webpack development server to run the application. Continue with part two here.

Great Angular, ASP.NET Core Starter Templates

Great Angular, ASP.NET Core Starter Templates Title

This post exists to provide hope. After the somewhat hesitant reaction to last week's webpack post, this post highlights two great Angular and ASP.NET Core starter templates. These starter projects not only contain configurations for webpack 1 and webpack 2, there are a ton of other Angular and ASP.NET features implemented for you.

ASP.NET Core Template Pack

ASP.NET Core Template Pack - Angular Starter

The first project template to note comes from the ASP.NET Core Template Pack by Mads Kristensen. The template pack is a Visual Studio Extension containing multiple templates for ASP.NET Core. The collection includes a helpful Angular template.

Key Features

The hallmark feature of the template is Angular Universal. This framework enables server-side Angular rendering when the application is initially requested from the server. Angular Universal decreases the initial page load time by rendering the first request on the server instead of requiring the client to wait for the application to download before rendering. Subsequent route changes are all rendered client-side. For any application that requires minimum load times, this framework is a must-see.

The template also uses webpack version 1. For anyone working on integrating webpack into their applications, every example helps. This template doesn't use the latest loaders that the folks on the Angular side of the community are using. It does however accomplish the primary goal by compiling combined scripts. And being that it's using Angular Universal, there are webpack configurations for both the client application and the server rendering portion.

The template also implements webpack's hot module replacement feature. This is a development feature where the client-side code is replaced in the browser via a WebSocket when the source module is edited on the file system. If you've ever used a development server with automatic refresh like Browser Link or Browsersync, then it's like that – except without the full page refresh. You should check out the template just to try out this feature – you may never go back.

The previously mentioned features are configured with the help of Steve Sanderson's ASP.NET Core JavaScript Services. This library is part of ASP.NET Core on GitHub and provides useful integrations for single-page application (SPA) frameworks including Angular. The template includes simple, foundational examples for routing and data access as well as Docker container configuration.

Gotchas

Currently, if you are looking for the very latest in front-end Angular features, this template doesn't have them. It's using webpack version 1 versus webpack version 2 and doesn't include all of the niceties that have been introduced with newer webpack loaders such as awesome-typecript-loader and angular2-template-loader.

Also, if you see an error like this when starting the template, it means you have to specify a prior version of ASP.NET using the global.json file.

Template Error

You can read more about the solution on GitHub.

Bottom Line

This is a stable template that highlights the main integration points between ASP.NET Core and Angular. For now, it might not have the most up-to-date client-side features but you can imagine these will come as the tooling comes to a final release (or when we all submit pull requests). Being a Visual Studio extension, it is optimized for the Visual Studio IDE and it provides a nice starting point.

To view the source for this template, check out its GitHub repository.

ASP.NET Core & Angular2 Universal Starter (Angular2Spa)

ASP.NET Core & Angular2 Universal Starter Diagram

This might be the best Angular on ASP.NET Core template available right now. It's actively developed by a member of the Angular Universal team, Mark Pieszak.

In fact during the course of writing this review, he fixed an issue that I was experiencing while working with the template for this article. The template contains some of the latest techniques and frameworks for building Angular applications on ASP.NET or otherwise.

Key Features

You can think of this project as a superset of the template currently in the ASP.NET Core Template Pack. It has the same base level of features and then adds on top of it. This template works in Visual Studio. It also contains configuration files to run in Visual Studio Code, complete with key bindings to initialize the dotnet build. It even uses the newer ASP.NET Core 1.1 assembly versions.

For the front-end tooling, the template uses webpack version 2 including many new loader patterns that you find in the Angular documentation. The example also makes use of an RxJS-based, Redux-like store via the NgRx library.

To help keep your code styles on par, this template uses codelyzer. Built on top of TSLint, codelyzer provides linting for TypeScript as well as the Angular Style Guide conventions.

Finally, this template configures unit testing with Karma and Jasmine integrated with webpack. It also includes end-to-end (E2E) tests using Protractor. These configurations alone can save you a dozen hours versus configuring yourself.

Gotchas

Certainly check out the README file for this project. It has a lot of great information on how to use Angular with or without this template and highlights areas where you may otherwise stumble. One other word of caution – this template leans on the bleeding edge so you may find some instability along the way. The maintainer so far seems to do a nice job keeping up with any logged issues.

Bottom Line

This is a must-see and must-watch starter template for ASP.NET developers using Angular. There is nothing more to say, take a look for yourself.

Final Thoughts

The Angular and ASP.NET frameworks are both evolving at a rapid pace and it can be difficult to keep current with the changes. These templates can help you start coding quickly and help you incorporate new technologies and patterns as they are published.

These are two of the best and more noteworthy starter templates for Angular and ASP.NET. Give them a try and share your thoughts below. If there are any other starter templates you find helpful, please let everyone know in the comments.

SystemJS to Webpack – Before You Begin

SystemJS to Webpack - Before You Begin Title

This is a primer discussing why to move from SystemJS to webpack in your Angular project. The post also describes some of the hurdles you may run into with this effort.

While it doesn't go into specific webpack configuration, this article aims to provide an overview for someone who has heard of webpack but doesn't quite understand why or how to get started. Specific details configuring webpack with ASP.NET Core are coming in future posts.

SystemJS – Starting Point

There are several posts on this blog with examples using SystemJS to mimic Node.js module resolution in the browser. While TypeScript understands how to resolve types by npm package name during development, SystemJS fills this need in the browser by mapping package names to specific script files within the npm packages. This is an effective and minimal way to setup an Angular 2 application to load its dependencies from npm and get going quickly.

After starting with SystemJS, applications (even small ones) quickly grow to contain hundreds of ES2015 modules that are then downloaded by the browser to execute the application. This is probably not a problem during development when the browser and the server are on the same machine.

At some point, however, you may want to host your application for others to see. The performance hit of loading this many modules becomes noticeable. This situation leads to creating some type of build combining these hundreds of requests into only a handful.

Whereas SystemJS manipulates the browser to understand node module resolution, webpack (and similar build tools) manipulate the code to accommodate the browser's needs. Whether SystemJS or webpack, you continue to code the way that makes you the most productive with as many ES2015 modules in as many files as make you happy. Unlike SystemJS, webpack provides the browser with a streamlined payload to load the application as efficiently as possible.

Destination Webpack

Webpack at its core is a JavaScript module bundler meaning it takes JavaScript modules from separate files and combines them into one file. Webpack can be extended, though, by using loaders which allow you to bundle other types of files. So, while webpack doesn't understand TypeScript natively, it processes .ts files by using a loader.

Webpack has another extensibility mechanism called plugins. Where loaders handle specific file types, plugins process the contents of the loaded files. Plugins, for example, are responsible for minifying code where variable names are shortened and whitespace is removed.

One aspect of webpack that is different from a tool such as grunt or gulp is that it is more declarative meaning you configure an object literal and that object defines what should happen when you run the webpack command. The declarative nature of webpack ideally requires less configuration code and should lead you into a build that is more optimized than if you had to wire the pieces up yourself.

There are two other npm packages that carry the webpack name but are not the bundler, itself. These packages work with the bundler to improve the development experience. They are webpack-dev-server and webpack-dev-middleware.

The webpack-dev-server npm package is a lightweight development server based on Node.js and Express. It has all the basic development server features and includes hot module replacement. Hot module replacement (HMR) is a process where the development server uses a WebSocket connection to replace the code of a given module in the browser when it has changed in the development environment. This does not require a full page refresh to reflect the updates making it seamless to view your changes in the browser during development.

The webpack-dev-middleware npm package is a little more esoteric. It runs as an in-memory store containing the build output as opposed to writing the output to disk. This can decrease the time between when you modify a file and when you can see the changes running in the browser.

Configuration Troubles

Currently in early 2017, webpack is moving from version 1 to version 2. This can lead to incompatibilities in the webpack tool chain. When configuring a webpack build yourself, expect to find out-of-date examples and see errors processing your build.

Not all loaders and plugins support both webpack 1 or webpack 2 and you may find yourself trying different versions of these npm packages with different versions of webpack. Throw in the fact that TypeScript is adding significant new features through all of this and it's a recipe for configuration troubles.

This isn't meant to scare you off, just be prepared. Expect to search through the GitHub issues of some of these frameworks looking for answers or expect to post issues yourself. It's not always clear which combination of settings are correct but the webpack community is by-and-large responsive and committed to this tooling.

This is an example of an issue you may find. While the TypeScript compiler allows comments in the tsconfig.json file as of version 1.8, some of the TypeScript webpack loaders will blow up if there are comments in this file. There is no helpful error message, it just throws an error that a property is not defined on a null reference.

It's likely these issues are resolved over time, but you should understand what you are up against.

Replacing SystemJS

Now that you know about webpack's advantages and potential pitfalls, what are the general steps required to replace SystemJS? This is a summary:

  • Move polyfill scripts from index.html into the webpack bundle
  • Compile and bundle the TypeScript files
  • Move any external component HTML templates and stylesheets inline
  • Add a reference to index.html for the new bundled resource

Move polyfill scripts from index.html into the webpack bundle

Many Angular tutorials instruct you to put polyfill dependencies directly in the index.html (or alternatively named default HTML) file. This approach works and will continue to work with webpack but you should consider moving these dependencies into the webpack configuration.

First, including these dependencies into a bundled request improves performance. Second, having all your script dependencies in one place decreases the time to find a given dependency – thus increasing maintainability. Third, webpack resolves dependencies through node module resolution meaning you add the package name to the configuration and you're done. Using SystemJS, you often must identify the correct script to load from within the npm package.

Compile and bundle the TypeScript files

While you could continue to compile TypeScript with the TypeScript compiler, the idea is to move everything into webpack. There are several loaders that handle TypeScript files and are generally straightforward to configure. A popular TypeScript loader in the Angular community now appears to be awesome-typescript-loader.

Move external component HTML templates and stylesheets inline

This one is Angular-specific. When you have components with external templates and/or stylesheets, Angular loads these files as separate requests. By using a loader like the angular2-template-loader, the build now can combine the contents of the external templates and stylesheets into the component script itself.

Note: Understand that by moving the templates and stylesheets inline, setting the @Component directive's moduleId property is unnecessary. Leaving the moduleId property set in the component can lead to issues. While the available tooling should most likely handle this, you may find you must account for this yourself by removing the settings manually or creating automation.

Add a reference to index.html for the new bundled resource

Finally, once you define the file(s) that webpack outputs, you must reference the new bundled file(s) in your main HTML file. As you may have guessed, there are even webpack plugins to handle this as well based off the webpack configuration (see html-webpack-plugin).

Additional Configuration

While the previous sections listed the necessary pieces of a basic webpack build. You have so many more options available to you to optimize your code. Some build steps to consider are: Ahead-of-time (AOT) compilation, using the wwwroot folder in ASP.NET Core, tree-shaking, running tests, and using the webpack development server with hot module replacement.

Tackling the Beast

So, you still want to get going with webpack? Where do you begin? There are many projects on Github and other sites that provide insight into how this tool works. It appears to still be hit or miss as far as quality. Looking at it from an ASP.NET developer's perspective, the guidance options are fewer. With that said, here are some links to get started:

None of these resources paint the full picture but they provide a good overview. In addition to these general resources, visit the documentation for the individual loaders and plugins to get details regarding compatibility with other packages.

You should consider creating your own webpack build from scratch. There are advantages to doing it this way. If you like knowing how these tools work, this is a rewarding way to learn, albeit a time-intensive one.

Focus on one piece of the build at a time. It's easier to identify which pieces of the build are failing and you can more easily investigate or replace that specific loader or plugin to compare results. Once you stabilize one part, move on to the next until you have the build that you want.

Start with the pieces outlined in the Replacing SystemJS section and continue from there.

More on the Way

You've learned why you should consider moving to webpack and the general approach to creating a webpack-based build. Again, this is just a primer. The plan is to ultimately provide you with a webpack configuration that works well with Angular and with ASP.NET Core. You will see more posts about webpack here soon.

For now, what are your favorite webpack resources? Did you build a template that others may find useful? Please share in the comments.