Angular 4 - Getting Hashed Webpack Bundles Working in ASP.NET Core MVC

2017-04-10

After playing a lot lately with the new angular-cli 1.0.0 which wraps around webpack, I ran into some issues making use of the -prod bundles produced by ng build.

Setup Webhook

Building the production version of your application with ng build -prod, the client generates bundles which have hashed names per default, like vendor.ea3f8329096dbf5632af.bundle.js. While in development mode, the bundle names do not have that hash; e.g. vendor.bundle.js.

The angular client can also generate a start page with the correct links to the hashed bundles. But I am not using the generated page, I want to use ASP.NET Core MVC views for the layout and start page.

This means, I have to reference those bundles somehow.

My naive approach was of course to include a simple <script type="text/javascript" src="/inline.bundle.js"></script> tags. This is totally working fine with the static names in development! In production though, this solution breaks of course.

Quick and Dirty

One (not recommended) solution is, to remove the hashing from the bundle generation. You can disable that via command line options:

ng build -prod --output-hashing=none

But those hashed bundles are great, because it solves a common issue with browser and proxy servers caching old versions of your scripts. Don't underestimate this as it can generate all kinds of strange behavior for users.

That means we need a solution which works with dynamically changing file names, just having URLs in the template wouldn't do it!

The Actual Solution

Luckily, ASP.NET Core MVC comes with some handy tag-helpers for style and script tags. The tag-helpers have include properties which allow you to have wildcards and patterns to include many scripts at once! The tag-helper then would generate as many <script> tags as needed...

But just having <script type="text/javascript" asp-src-include="*.js"></script>, which would include all .js files in wwwroot, will not work either unfortunately, because the order of how the scripts are loaded matters. But we can still use it:

My _layout.cshtml now looks more like this:

<head>
 ...
     <environment names="Development">
    </environment>
    <environment names="Staging,Production">
        <link rel="stylesheet" asp-href-include="~/styles*.css" />
    </environment>
</head>
<body>

...

<environment names="Development">
    <script type="text/javascript" src="/inline.bundle.js"></script>
    <script type="text/javascript" src="/polyfills.bundle.js"></script>
    <script type="text/javascript" src="/scripts.bundle.js"></script>
    <script type="text/javascript" src="/styles.bundle.js"></script>
    <script type="text/javascript" src="/vendor.bundle.js"></script>
    <script type="text/javascript" src="/main.bundle.js"></script>
</environment>
<environment names="Production,Staging">
    <script type="text/javascript" asp-src-include="~/inline*.js"></script>
    <script type="text/javascript" asp-src-include="~/polyfills*.js"></script>
    <script type="text/javascript" asp-src-include="~/scripts*.js"></script>
    <script type="text/javascript" asp-src-include="~/vendor*.js"></script>
    <script type="text/javascript" asp-src-include="~/main*.js"></script>
</environment>
...

The naming patter of the hashed bundles is to have the hash between the script name and the extension: [name].[hash].bundle.js.

All we have to do is, replace the hash position with *, e.g. asp-src-include="~/vendor*.js".

Also, pay attention to the styles bundle. In development, the styles are also bundled as JavaScript, to make e.g. HMS work. In production though, it will be a css file which should go into the <head> section!

What are your thoughts? Let me know in the comments!