Using Hot Module Replacement Feature Of Webpack In ASP.NET Core

Webpack is the leading Module Bundler nowadays. The ability to generate and load static assets based upon your needs is what makes Webpack different from other build systems, like Gulp and Grunt. Gulp and Grunt work at individual levels whereas Webpack works at a higher level, keeping track of your entire project; and can be configured extensively to behave differently in development and production environments.

In this post, we will see how to use one of Webpack's  most exciting features known as "Hot Module Replacement" which is the part of Webpack-dev-middleware. We won't discuss all the ins and outs of Webpack because we will see it in detail in a future post. Here, we will see how to use Hot Module Replacement (HMR) in ASP.NET Core with Typescript and Single-Page Application (SPA) services in its simplest form.

Hot Module Replacement

HMR is the ability to load (but not limited to) JavaScript modules in the running application without reloading the entire page.
 
Pressing refresh each time, we make a change in our code that reminds me the time when I used to write my HTML code in Windows Notepad and run it in the browser for testing. The situation becomes even worse when working on an SPA application with multiple views or in a specific view containing multiple tabs or pagination. The reason behind this is that we have to navigate thoroughly again and again each time we make a change.

Setting up ASP.NET Core Project in Visual Studio

Now, let's configure an ASP.NET Core MVC application from scratch in Visual Studio so that we can understand each step clearly. So, in Visual Studio, go to -

  • New Project => .NET Core => ASP.NET Core Web Application;
  • Configure DI MVC services and add Controllers and Views with Layouts;
  • Add "package.json", "tsconfig.json", and "webpack.config.json" files at the root;
  • Add and install the Webpack and other NPM packages as Dev Dependencies in the "package.json" file.

Adding necessary NPM packages

The following NPM packages are the minimum requirement for HMR to work with ASP.NET Core.

package.json

  1. {  
  2.     "devDependencies": {  
  3.        "@types/node""6.0.62",  
  4.        "aspnet-webpack""^1.0.11",  
  5.        "typescript""^2.1.5",  
  6.        "ts-loader""^0.8.2",  
  7.        "webpack""^1.12.14",  
  8.        "webpack-hot-middleware""^2.10.0"  
  9.     }  
  10. }  

In the above "package.json file", "typescript" and "webpack" are the standard NPM packages that we need for running TypeScript and Webpack. "ts-loader" package is the Webpack loader (Think of it as plugin in Gulp\Grunt) for TypeScript. @types/node contains the type definitions for Node.js; "aspnet-webpack" and "webpack-hot-middleware" are required by ASP.NET Core SPA Services that we will add next.

Configuring TypeScript

Add the following TypeScript definition in the "tsconfig.json" file.

tsconfig.json

  1. {  
  2.   "compilerOptions": {  
  3.     "noImplicitAny"false,  
  4.     "noEmitOnError"true,  
  5.     "removeComments"false,  
  6.     "emitDecoratorMetadata"true,  
  7.     "experimentalDecorators"true,  
  8.     "sourceMap"true,  
  9.     "target""es5"  
  10.   }  
  11. }  

Configuring Webpack

Next, configure Webpack so that it resolves our own custom TypeScript file against the "ts-loader" that we just installed as NPM package. So, add the following configuration in the webpack.config.js file.

webpack.config.js

  1. var path = require('path');  
  2. var webpack = require("webpack");  
  3.    
  4. module.exports = {  
  5.     resolve: { extensions: ['''.js''.ts'] },  
  6.    
  7.     entry: { "main-client""./wwwroot/ts/app.ts" },  
  8.     output: {  
  9.         filename: '[name].js',  
  10.         path: path.join(__dirname, './wwwroot/dist'),  
  11.         publicPath: '/dist/'  
  12.     },  
  13.    
  14.     module: {  
  15.         loaders: [  
  16.             { test: /\.ts/, include: '/wwwroot/ts/', loader: 'ts' }  
  17.         ]  
  18.     }  
  19. };  

We will put our TypeScript file (app.ts) in ts folder at the web root (i.e. wwwroot), so we configured the entry: section of the configuration to point to that file. We then configured Webpack to output the bundled JavaScript file in the dist folder at the web root, as shown in the output: object in the configuration file.

The file name will be resolved against the [main-client].js JavaScript in the "dist" folder and finally, configure the ts-loader below the output: configuration and include path to our TypeScript file.

Adding ASP.NET Core SPA Services

Now, we need to add the SPA Services for ASP.NET Core that Webpack offers. Webpack offers webpack-dev-middleware which serves the files emitted from Webpack to a connected Server, such as Kestrel or webpack-dev-server. These services will be available for other programming languages as well, such as Python, PHP etc., in the future. You can use Angular and React client framework as part of this Dev middleware which, in-turn, uses another NuGet package called NodeServices.


You are not limited to use only Angular or React frameworks but you can also use your own packages or configure for your project. For this, you have to work directly with Microsoft.AspNetCore.NodeServices.

For now, we will look only at a single Typescript file to see HMR in action. To use the webpack-dev-middleware, you have to add this NuGet Package from where you can add the webpack-dev-middleware as Middleware in Startup.cs.

To add SPA services in the project, open the NuGet Package Manager and search for Microsoft.AspNetCore.SpaServices,


Install the package, open Startup.cs class, and add the following (from lines 8-11) Dev Middleware in the request pipeline:

Startup.cs

  1. using Microsoft.AspNetCore.SpaServices.Webpack;  
  2. public class Startup { ...  
  3.     if (env.IsDevelopment()) {  
  4.         app.UseDeveloperExceptionPage();  
  5.         app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {  
  6.             HotModuleReplacement = true  
  7.         });  
  8.     }...  
  9. }  

Do not forget to enable HotModuleReplacement to true here at line 10. Now, everything is setup. Create an h2 header element in Index.cshtml with id "app", create a ts folder in wwwroot, add a app.ts file in the folder, and add the following content in the file.

app.ts

  1. if (module['hot']) {  
  2.     module['hot'].accept();  
  3. }  
  4. var message = () => "Hello World";  
  5. document.getElementById("app").innerHTML = message.apply(this);  

A module can only be updated if you accept it. This is what the lines 1-3 do. You must add these lines in order for HMR to work against your modules. Finally, open the Webpack Task Runner window and run the "Development" task of Webpack by right-clicking on it and click "Run". After running the Webpack, it will bundle up the compiled TypeScript file and will create a "dist" folder in the "wwwroot" containing the bundle.


If you don't have the Webpack Task Runner Explorer Visual Studio Extension, you can download it from here.

Reference this main-client.js at the bottom of the body tag in _Layout.cshtml and run the application. If everything works well, you will see a [HMR] Connected message in the browser's console as,


This means that your HMR up and running. Now, try to modify the TypeScript file and hit "Save". You will see that the changes in the browser reflected immediately without reloading the page.



Webpack HMR can also be used with React and Angular Services in ASP.NET Core as we will see in upcoming posts. The ASP.NET Core and Angular\React templates that you case as "ASP.NET Core Templates Pack" for Visual Studio or by using Yeoman's generator-aspnetcore-spa, use the same HMR for Hot-Reloading. You can find more about the templates from here.