Aurelia skeleton with webpack and ASP.NET Core

Configuration for creating new Aurelia applications

Mikko Vuorinen
5 min readMar 4, 2018

--

Configuring Aurelia is not difficult, but to get everything set up — so that the development, debugging and deployment of your application is quick and easy — is not a quick or an easy task. Last few days I have spent some time to build up configuration that works the best for me.

The configuration I ended up with is available on GitHub at vuorinem/aspnet-aurelia-webpack-skeleton. This post explains how and why I ended up with that specific set up.

ASP.NET Core

The core of the skeleton is based on an empty ASP.NET Core template. It provides a simple file server, with MVC added to it in order to create an index page using SPA fallback routing and tag helpers.

app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=SpaIndex}/{action=Index}");

routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "SpaIndex", action = "Index" });
});

The asp-append-version tag helper acts as an alternative to adding a content hash to the filename and updating references within the html page:

<script src="./dist/main.js" asp-append-version="true"></script>

Aurelia

Aurelia is currently my favourite front-end framework. The main reason is the simplicity and cleanliness of all framework-related code in the application. The framework API is remarkably unobtrusive, giving the developer freedom to build the application the best way they see fit.

In the skeleton, Aurelia is configured in the main.ts file that is the entry point of the application. For most uses, standard configuration is a good start:

aurelia.use.standardConfiguration();

The one line sets up the default binding language, template resources, event aggregator, browser history implementation and router. These could be set up individually if some of them are not needed and could be removed (to reduce bundle size).

Development logging is only enabled when debuggin (see later for setting up the __DEBUG__ flag):

if (__DEBUG__) {
aurelia.use.developmentLogging();
}

Webpack

Configuring webpack is definitely the most complicated part of the set up. Fortunately with the release of webpack 4, it is becoming much less of a pain. With the skeleton I decided to start from an empty configuration file, got a single JavaScript file “bundled” and running on the browser, and worked my way up step by step, adding more files, TypeScript transpiling, CSS loading with autoprefixer, and SASS compiling. Then I configured webpack-dev-server with HMR (Hot Module Reloading), split the configuration for different use cases, and finally added some configuration and optimisation for production builds.

There are four options for running webpack, all of which can be started up using the scripts defined in package.json:

"scripts": {
"start": "webpack-dev-server --config webpack.development-hmr.js --open",
"watch": "webpack --config webpack.development.js --watch",
"build": "webpack --config webpack.development.js",
"publish": "webpack --config webpack.production.js"
},

When using Visual Studio, the build script will automatically run after building the project, and publish script when publishing. This is defined in .csproj file (that is much cleaner in Core compared to the traditional .NET Framework), for example:

<Target Name="WebpackPublish" AfterTargets="BeforePublish">
<Exec Command="yarn run publish"></Exec>
</Target>

Scripts watch and start need to be started up manually.

Watch can be used to automatically build new version of the application when saving changes to a file, which works when running the application using IIS Express from Visual Studio or the .NET runtime.

Start on the other hand will start up a separate web server and open the application with the browser, automatically loading changes to the browser when saving any of the source files. Because the .NET application is not running, an alternative index page is needed for this type of development. This is included in webpack-devserver-root folder.

One of the key goals of developing the skeleton was to provide a configuration that works in different scenarios and for different users. In the simplest case, developer opens up the solution in Visual Studio and expects it to work by clicking Start. For more complicated uses, say developing a new feature or fixing a complex bug, using a separate dev server and HMR allows much faster inner loop of development (code-run-debug), saving developer’s time and focus.

The resulting webpack configuration is fairly simple and straightforward. One tweak I added was the __DEBUG__ flag that is set to true for development builds and false for production builds:

new webpack.DefinePlugin({
__DEBUG__: JSON.stringify("true"),
}),

The value of the flag can be used in any source file, but TypeScript requires it to be declared before using it, so this line is added to the main.ts:

declare const __DEBUG__: boolean;

Output

As described above, there are different ways of using the skeleton, and these result in different output. For development, everything is bundled up in dist/main.js in the wwwroot (for ASP.NET Core server) or webpack-devserver-root. Production build will extract CSS into a separate app.css file that is referenced in the index page when running in production.

Note that the ExtractTextPlugin for webpack 4 is currently a pre-release, so using it requires adding “@next” as a version range when installing the package.

Webpack output for production can be optimised for size by switching the mode-configuration from development to production. In addition, our configuration sets the NODE_ENV environment variable to production, which some libraries use for optimising their output. With the very simple sample application, the size of main.js drops from 2 295 KB (majority of which is sourcemaps) to 293 KB + 150 KB in app.css.

Further development

The configuration set up for the skeleton is by no means exhaustive. There are several improvements that would be useful for most of the applications, and these will most likely be included in the skeleton in near future.

Polyfills

Aurelia itself uses Promises, which are not supported by Internet Explorer. In addition to switching the TypeScript target to ES5, supporting IE would require adding a Promise polyfill (like Bluebird) to the bundle and defining the global Promise variable to use the polyfill.

Code splitting

As the size of the application grows, and the number of dependencies increases, using just one bundle for all the code can easily become the bottleneck of the application start up performance. Webpack’s tools for code splitting can be extremely useful in keeping the load times under control.

Thank you for reading! Please check out the source code from Github, and feel free to give any feedback or comments.

--

--