Using Webpack and TypeScript with ASP.NET Core 2

When we wanted to start using TypeScript and Webpack with JamHost, the Internet was pretty scarce on how to get everything working together seamlessly. This guide will cover everything you need to know and do in order to get Webpack and Typescript working with an ASP.NET Core 2 project.

This guide will also include information about how to set up and use jQuery, Bootstrap and TinyMCE in this system.

Prerequisites

Make sure you have yarn installed (you could also use npm, but we recommend yarn). Once you’ve done that, initialise the package.json file in the same directory as your C# project, with:

yarn init

and follow the prompts to set up your new package.json. Then you need to start installing the dependencies that are required for TypeScript and Webpack:

yarn add core-js
yarn add --dev awesome-typescript-loader css-loader extract-text-webpack-plugin file-loader html-webpack-plugin ts-node typescript uglifyjs-webpack-plugin webpack

Optional: jQuery

If you’re using jQuery in your site, include the required dependencies with:

yarn add jquery
yarn add --dev @types/jquery

Optional: Bootstrap

If you’re using Bootstrap in your site, include the required dependencies with:

yarn add bootstrap
yarn add --dev @types/bootstrap

Optional: TinyMCE

If you’re using TinyMCE in your site, include the required dependencies with:

yarn add tinymce
yarn add --dev @types/tinymce

Create the directory structure for app bundles

We’re going to set up Webpack so that it bundles up client-side code residing in app and emits it to wwwroot, where it’ll then be packaged up by dotnet publish automatically.

Under your project folder, create an app folder and the appropriate child directories such that you have:

- app
- js
- bundles

Each TypeScript file underneath bundles will act as our entry points that we can include in Razor views.

Configuring Webpack and TypeScript

We now need to configure Webpack and TypeScript to work in your project. Create the following files in the directory for your C# project:

Now with this Webpack configuration, there’s a bunch of different entry points which generate “chunks”, and then the HTML webpack plugin at the bottom defines how we’re going to use these chunks in Razor.

How are the bundles and entrypoints configured?

You’ll notice that there are manually specified entry points for things like “jquery” and “font-awesome”; if you look in the HTML webpack plugin, you can see that we’re implicitly including these in any bundle name that starts with “page-” (that is, any .ts file in the bundles folder that starts with page-). When you set up your global Razor layout file (used by all your views), you’ll include one of these “page-” bundles to get all of the common things on your website included (like Bootstrap CSS).

As part of these manual entrypoints, we also include polyfills, which uses core-js to provide the runtime dependencies of TypeScript in older browsers. This is included as the first chunk in the “page-” bundles.

Where does my emitted content go?

When Webpack builds these bundles, there’ll be wwwroot/js, wwwroot/css and wwwroot/assets emitted. The Javascript is emitted directly by Webpack, while the CSS is emitted from the ExtractTextPluginand the assets are copied out based on the last file-loaderrule.

However, you don’t need to worry about referencing this content directly in Razor. The HTML plugins at the bottom of this configuration generate Razor partials, which we’ll cover a little bit later.

Optional: jQuery

For jQuery, we use this jQuery shim to ensure that the $ variable gets exported to window correctly when it’s included. By default when Webpack bundles up jQuery directly, that doesn’t occur (as it’s intended to be used with import). So we use this shim so that other bundles on our page can access that global. This file goes underneath app/js.

When you want to use jQuery’s $ variable inside other bundles, tell TypeScript about the reference with:

/// <reference path="../../../node_modules/@types/jquery/index.d.ts" />

Optional: TinyMCE

For TinyMCE, we need to tell Webpack to create a bundle of the TinyMCE resources so they get deployed, even though we never reference them anywhere. TinyMCE at runtime will add references to the correct skin files inside the iframe it uses. If we were to reference the resources directly, then the TinyMCE styles would get applied to our page itself, which is definitely not what you want. So include this file inside app/js to create the correct references for TinyMCE:

Wire up Webpack to your C# project

Now we need to tell MSBuild to run Webpack on build so that the assets exist when your C# web app is running. Open up the .csproj file itself in something like Visual Studio Code, and update the bottom of it to contain something like this:

There’s some important configuration sections to cover here, which are required to get Webpack building correctly inside MSBuild.

WebpackInputs

This tells MSBuild what files Webpack will be reading from. MSBuild doesn’t actually pass these files to Webpack, but instead uses them as part of incremental compilation. If all of the outputs are newer than the inputs, then MSBuild won’t run the BuildClientAssets target, saving you time when building your project.

The WebpackInputs should list all of the possible files Webpack might read as an input; here we’ve listed everything under the appdirectory and the configuration file webpack.config.ts itself.

WebpackOutputs

This tells MSBuild what files Webpack might output. It’s paired with WebpackInputs so that incremental compilation works.

Since we can’t tell ahead of time what files Webpack will generate, we list all the current files in the directories where we expect Webpack to output files.

In the case where nothing has been built yet, there won’t be any output files; MSBuild by default treats this as “nothing needs to be built”, so we have to trick MSBuild into running our target by specifying a non-existent file as part of the outputs if there aren’t any output files already.

Razor Precompilation

Finally we configure the targets (including the Webpack build) so that it runs before Razor precompilation. We’re emitting a bunch of partials from Webpack, so we need to make sure that happens before Razor compiles them to an assembly.

Referencing compiled assets from .NET with Razor

With the Webpack configuration we’ve specified, the generated Javascript, CSS and asset filenames contain hashes of their content. This is great for cache busting when deploying new versions of your application, but means that we can’t just hard-code known paths to the generated files. Instead, we need some way of emitting the paths from Webpack that will allow Razor to reference them.

To do this, we generate partial Razor views on build, to the Views/Shared/Assets directory. For this to work, we need to add some template files to that directory that Webpack will use to generate the actual partials:

With this set up, we’ll now get a nice list of partials generated from Webpack that we can use in our Razor views:

An example of generated Razor partials from Webpack

To make these easier to include, we created a static helper class in C#:

Finally, using all of this

We can now finally use all this infrastructure we’ve just put together. We’ll cover two examples here, one for setting up common includes for your global Razor view with a “page-” bundle, and one for some generic behaviour that we might want to include as part of another partial or component.

Including common resources with a “page-” bundle

First create a page-common.ts file inside app/js/bundles, common.cssinside app/css, and update your Views/Shared/_Layout.cshtml to make the same calls as in the example:

When Webpack builds this, you’ll end up with a file at wwwroot/js/page-common.<hash>.min.js, wwwroot/css/page-common.<hash>.min.css, Views/Shared/Assets/_Gen_page-common_Scripts.cshtml and Views/Shared/Assets/_Gen-page-common_Styles.cshtml. The JS and CSS will be minified, but if you take a look at the generated Razor views, this is what you will see:

jQuery and other chunks are included because in our Webpack configuration for the HTML plugin for “page-” based bundles, we’re including these chunks explicitly. Looking back on our webpack.config.ts file, we can see this:

Including specific behaviours in pages

When you create a bundle that does not start with “page-”, this indicates it’s a general behaviour you want to include in a page (without including common resources like jQuery). Other than that it works the same as “page-” bundles.

Here’s an example of a bundle which loads some data from a page and shows an alert with that data after the page has loaded. This goes under app/js/bundles.

We can then use that in a Razor view like this:

Remember if you want to have other TypeScript scripts that aren’t bundles of their own to place them under app/js directly (or some subdirectory other than bundles), then you can reference them in TypeScript with import { Stuff } from '../othersubdirectory/script.ts';.

Summary

Hopefully this helps you get started with Webpack and Typescript in ASP.NET Core 2. We’re currently using this solution in JamHost, our game jam hosting service for game developers.

If you’re interested in updates, follow us here on Medium, on Twitter or on Facebook.