Bundling in .NET Core MVC Applications with webpack and typescript

What Is Bundling And Why Would You Need It?

A little about how web browsers work

<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Awesome Site</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
crossorigin="anonymous">
<link rel="stylesheet" href="/css/site.css" />
</head>
<body>

<div class="container body-content">
<div class="row">
<div class="col-xs-12">
<h3>The best content ever!</h3>
</div>
</div>

< /hr>
<footer>
<p>Why're you looking down here? The content is up there!</p>
</footer>

</div>

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
<script src="/js/site.js"></script>
</body>

</html>
Google Chrome’s network tab showing a selection of the get requests needed to render Amazon UK’s front page

Bundling in .NET Core MVC Applications

Options

  • Gulp
  • Grunt,
  • Bower,
  • webpack
  • BundlerMinifier.Core

webpack

  • searching for products
  • account management (signing up, viewing orders, changing payment details, etc.)
  • providing feedback

webpack Worked Examplex

Empty Web Application

yo aspnet
cd webpack-ts
dotnet restore

Adding MVC

"Microsoft.AspNetCore.Mvc" : "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
dotnet restore
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

builder.AddEnvironmentVariables();
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
// here is where we add the MVC service
services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// here is where we'll add the Static Files service
app.UseStaticFiles();
// and here is where we add our routing
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}

Adding Controllers and Views

The webpack-ts project, when initially opened in VS Code
using Microsoft.AspNetCore.Mvc;

namespace webpack_ts
{
public class HomeController : Controller
{
// GET: /<controller>/
public IActionResult Index()
{
return View();
}

public IActionResult SayName()
{
return View();
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack.ts</title>
<link rel="stylesheet" href="~/css/bootstrap3-custom/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; @System.DateTime.Now.Year - GaProgMan</p>
</footer>
</div>

<script src="~/js/site.js" asp-append-version="true"></script>
</body>
</html>
<h1 id="greeting"></h1>

<script src="~/app/bundle.js"></script>
<h1 id="greeting"></h1>

<script src="~/app/someOtherBundle.js"></script>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}

Adding TypeScript Support

npm i -g typescript@next
npm i -g typings

Adding TypeScript Files

// create the class Greeter and export it
export class Greeter {

constructor(private message: string){
}

sayHello(){
console.log(this.greetingMessage);
}

get greetingMessage() : string{
return `Hello ${this.message} from TypeScript within a webpack bundle`;
}
}
constructor(private message: string){
}
get greetingMessage() : string{
return `Hello ${this.message} from TypeScript within a webpack bundle`;
}
import { Greeter } from './greeter'

export class Main {
private greeter: Greeter;
constructor(private defaultElementId: string) {
this.greeter = new Greeter("there");
}

sayHello () {
this.greeter.sayHello();
document.getElementById(this.defaultElementId).innerHTML =
this.greeter.greetingMessage;
}

get greetingMessage() : string {
return this.greeter.greetingMessage;
}
}

// testing Main class
var instanceOfMain = new Main('greeter');
instanceOfMain.sayHello();
export class someOther {
private myName: string;
private elementId: string;

constructor(defaultName: string, idOfElement: string){
this.myName = defaultName;
this.elementId = idOfElement;
}

sayMyName() {
console.log(`${this.myName}`);
document.getElementById(this.elementId).innerHTML = `Hello ${this.myName}`;
}
}

var instanceOfSomeOther = new someOther('Geoff', 'greeting');
instanceOfSomeOther.sayMyName();

Adding tsconfig

{
"compilerOptions":{
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"sourceRoot": "content/js",
"outDir": "wwwroot/app"
}, "exclude":[
"node_modules"
]
}
  • Transpile to ECMA Script 5
  • Use CommonJs’s Module pattern
  • Include a Source Map (these allow us to place breakpoints in the JS in Chrome’s dev tools, for instance)
  • Where the root of the source files are (/content/js)
  • Where to place the transpiled files

Adding webpack

npm i -g webpack@next
tsc
Our transpiled javascript source and map files

Adding awesome-typescript-loader

npm i -g awesome-typescript-loader

Adding a webpack.config.json

webpack 1.14.0
Usage: https://webpack.github.io/docs/cli.html

Options:
--help, -h, -?
--config
--context
--entry
--module-bind
--module-bind-post
--module-bind-pre
--output-path
--output-file
--output-chunk-file
--output-named-chunk-file
--output-source-map-file
--output-public-path
--output-jsonp-function
--output-pathinfo
--output-library
--output-library-target
--records-input-path
--records-output-path
--records-path
--define
--target
--cache [default: true]
--watch, -w
--watch which closes when stdin ends
--watch-aggregate-timeout
--watch-poll
--hot
--debug
--devtool
--progress
--resolve-alias
--resolve-loader-alias
--optimize-max-chunks
--optimize-min-chunk-size
--optimize-minimize
--optimize-occurence-order
--optimize-dedupe
--prefetch
--provide
--labeled-modules
--plugin
--bail
--profile
-d shortcut for --debug --devtool sourcemap --output-pathinfo
-p shortcut for --optimize-minimize
--json, -j
--colors, -c
--sort-modules-by
--sort-chunks-by
--sort-assets-by
--hide-modules
--display-exclude
--display-modules
--display-chunks
--display-error-details
--display-origins
--display-cached
--display-cached-assets
--display-reasons, --verbose, -v

Output filename not configured.
module.exports = {
"entry":{
"bundle": "./content/js/main.ts",
"someOtherBundle": './content/js/someOther.ts'
},
"output": {
"path": __dirname + "/wwwroot/app",
"filename": "[name].js"
},
"resolve": {
"extensions": ['', '.ts', '.webpack.js', '.web.js', '.js']
},
"devtool": 'source-map',
"module": {
"loaders": [
{
"test": /\.ts$/,
"loader": 'awesome-typescript-loader'
}
]
}
};
"entry":{
"bundle": "./content/js/main.ts",
"someOtherBundle": './content/js/someOther.ts'
},
  • Find the file ./content/js/main.ts
  • Read it’s contents and find any modules that it lists as dependencies (via the import keyword)
  • Recursively read the contents of the modules that they rely on
  • Place these files in memory, ready for outputting
"output": {        
"path": __dirname + "/wwwroot/app",
"filename": "[name].js"
},
"resolve": {
"extensions": ['', '.ts', '.webpack.js', '.web.js', '.js']
},
  • <blank extension>
  • .ts
  • .webpack.js
  • .web.js
  • .js
"devtool": 'source-map',
"module": {
"loaders": [
{
"test": /\.ts$/,
"loader": 'awesome-typescript-loader'
}
]
}

Running Everything

webpack
[at-loader] Using typescript@2.2.0-dev.20161226 from typescript and "tsconfig.json" from /path/to/source/webpack-ts/tsconfig.json.
[at-loader] Checking started in a separate process...
[at-loader] Ok, 0.001 sec.
Hash: 438ca430aa379e6d358d
Version: webpack 1.14.0
Time: 1320ms
Asset Size Chunks Chunk Names
bundle.js 2.92 kB 0 [emitted] bundle
someOtherBundle.js 2.06 kB 1 [emitted] someOtherBundle
bundle.js.map 3.18 kB 0 [emitted] bundle
someOtherBundle.js.map 2.44 kB 1 [emitted] someOtherBundle
+ 3 hidden modules
[at-loader] Using typescript@2.2.0-dev.20161226 from typescript and "tsconfig.json" from /path/to/source/webpack-ts/tsconfig.json.
[at-loader] Checking started in a separate process...
[at-loader] Ok, 0.001 sec.
Hash: 438ca430aa379e6d358d
Version: webpack 1.14.0
Time: 1320ms
Asset Size Chunks Chunk Names
bundle.js 2.92 kB 0 [emitted] bundle
someOtherBundle.js 2.06 kB 1 [emitted] someOtherBundle
bundle.js.map 3.18 kB 0 [emitted] bundle
someOtherBundle.js.map 2.44 kB 1 [emitted] someOtherBundle
+ 3 hidden modules
dotnet run

Accessing Your Pages

And That’s About It

--

--

--

I’m a .NET developer specialising in .NET MVC websites and services, and I blog about .NET Core things

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jamie

Jamie

I’m a .NET developer specialising in .NET MVC websites and services, and I blog about .NET Core things

More from Medium

Inside ASP.NET Core 6 React Template

CloudFront Signed URLs | AWS + .NET Core | Part 3

The Facilis framework for dotnet core — the plan

MongoDB C# Driver, clean and manageable database queries using dynamic predicates.