Last Updated: 12/30/2015
I recently embarked on a Single Page Application (SPA) project in which React was to be used with ASP.NET Core 1.0 (RC1) and Webpack. Due to some single sign-on security requirements, the decision was made to initially render the application from an ASP.NET MVC 6 view. Barring this constraint, the more traditional index.html file would have easily sufficed as the application’s shell. What did this MVC view buy me? It allowed me to leverage my C# skillset to satisfy the security requirements in an MVC controller. Specifically, I needed to analyze some HTTP request header values.
The requirement described above undoubtedly complicated things, but there was another conundrum: cache busting. A good cache busting strategy must be mindful of two extremities in particular: the development workflow and the production environment. During development, a unique collection of file names should be produced for each Webpack build. If clearing the browser’s cache immediately following a build of client-side assets has become commonplace in your development workflow, carve out some time to identify the underlying problem. When deploying to production, the files which changed since the last Webpack build should be assigned new, unique file names. After all, the application’s users shouldn’t be inconvenienced due to poor planning on part of the development team. Give the customer one less reason to consider doing business with the competitor.
Making Sense of Webpack Hash Keys
Webpack is capable of yielding file names which are supportive of the cache busting obligation. This is made possible by a couple of hash key placeholders which can be used in the output section of the Webpack configuration file. Those placeholders are:
- [hash] – A generated value which is unique to a build.
- [chunkhash] – A generated value which is unique to each chunk in a build.
These hash keys are made visible in the Webpack build output (see screenshot below). For example, the hash key used in the vendor.c7a466d957209719c8d9.js file name was produced with the chunkhash placeholder. The 67f2bdb1268bed71d35e hash key provided near the top of the build output represents the hash placeholder value.
This walkthrough will utilize chunkhash; however, there’s additional work to be done outside of this placeholder. Namely, the values assigned to the chunkhash placeholder must be extracted. Without these extracted bits, it’s impossible to reference the requisite client-side assets which were run through the Webpack build.
After a bit of research, I stumbled upon an npm module called html-webpack-plugin. This utility seemed promising, as it generates an index.html file with ease. My hope was that it could be adapted to a C# Razor view. Hours of hacking passed, and while I came close, I was unable to get this working to my satisfaction. Mission aborted, and onto the next option for solving the problem.
Striking Gold with the Webpack Assets Plugin
The assets-webpack-plugin was the next plugin I tried, and this did the trick. In a nutshell, this plugin generates a JSON file containing the generated file/chunk names. Unless configured otherwise, the JSON file’s default name is webpack-assets.json. I’ve changed the file name slightly, to webpack.assets.json, for demonstration purposes. Assume that my Webpack configuration file will produce two chunks: app and vendor. The resulting webpack.assets.json file will look as follows:
The supporting ES6-based Webpack configuration file could look as follows:
Direct your attention to line 41 in the gist provided above. This is where the assets-webpack-plugin configuration is defined. In review, the plugin configuration section is accomplishing the following three tasks:
- naming the generated file webpack.assets.json
- copying the generated JSON file to the build folder
- enabling pretty printing/formatting of the JSON
Parsing the JSON File
An object of type JObject is returned. With that object in hand, the MVC controller is equipped to fetch the desired file names and inject them into the ViewBag for retrieval in the view.
There was one challenge in particular that’s worth noting in regards to the helper method provided above. The helper method expects a parameter value representing the fully-qualified path to the project’s root folder. This value allows the method to locate the package.json file and the generated build folder. Since the application is compiled against .NET Core 1.0, we only have a subset of the features provided in the full .NET Framework (e.g., 4.6). In short, the old way of doing this (AppDomain.CurrentDomain.BaseDirectory) isn’t supported in .NET Core. The new approach to retrieving this desired path involves using the IApplicationEnvironment interface from the Microsoft.Extensions.PlatformAbstractions namespace. ASP.NET Core 1.0 is adorned with dependency injection, so constructor injection can be used to handle this:
Wiring up the Generated Assets to the MVC 6 View
The Index action method corresponding to the MVC view looks as follows:
Line 5 invokes the helper method described in the previous section. Lines 6 and 7 retrieve specific file names from the JObject and inject them into the view via the ViewBag.
In the view, the two script tags appearing immediately before the closing body tag look like this:
Since we’re leveraging ASP.NET Core 1.0 and running under IIS, there’s something worth noting here. By default, all static assets in the application are served from the wwwroot folder. Without any additional configuration, an attempt to serve files outside of that folder will fail. What additional configuration is required to serve files from the generated build folder? A hosting.json file must be created at the project root, and its contents should be nothing more than the following:
Attempting to learn Webpack can be daunting, especially since the official documentation leaves much to be desired. Couple that with the moving target that is ASP.NET Core 1.0, and it becomes quite the chore to get the two working together. Introduce the cache busting requirement for client-side assets, and you’re involved in a wild goose chase trying to get all the moving pieces stitched together. The good news is that all the hard work leads to a very robust solution.
Hopefully I’ve saved someone a few headaches with this blog post. The complete sample application described in this post can be found in this Git repository.