Self-hosting an Angular 2 website in a windows service using Webpack

In this post I am going to take my previous attempt at self-hosting an Angular 2 website in a windows service:

Self-hosting an Angular 2 website in a windows service

...and make it truly self-hosted. In the previous post I kind of cheated by using using SystemJS, loading Angular from CDN links, and compiling my app on the fly. In this post I'm going to instead use npm packages to get the libraries I need, and use Webpack to compile the code down into javascript I'll serve locally.

All of the code for this example is hosted in my GitHub repository.

Here's the basic process we'll follow:

  1. Create a package.json file with the libraries we need
  2. Create a tsconfig.json file to tell Visual Studio how to treat our typescript files
  3. Use the typings library to pull in the d.ts files we need to compile everything cleanly
  4. Create a webpack config file to compile our code
  5. Wire webpack into Visual Studio, using npm, so compilation happens automatically

So let's get started!

package.json

The first thing we'll need to pull in the npm packages we'll be using. To get started I'm going to open a console window into the Service.Website folder and run npm init:

C:\code\github\experiments\topshelf-angular2-service-webpack\Service.Website [master]> npm init  
This utility will walk you through creating a package.json file.  
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields  
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and  
save it as a dependency in the package.json file.

Press ^C at any time to quit.  
name: (Service.Website)  
Sorry, name can no longer contain capital letters.  
name: (Service.Website) service.website  
version: (1.0.0)  
description: An example that hosts Angular 2 inside a Windows service  
entry point: (index.js)  
test command:  
git repository:  
keywords:  
author: Sam Storie  
license: (ISC)  
About to write to C:\code\github\experiments\topshelf-angular2-service-webpack\Service.Website\package.json:

{
  "name": "service.website",
  "version": "1.0.0",
  "description": "An example that hosts Angular 2 inside a Windows service",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Sam Storie",
  "license": "ISC"
}


Is this ok? (yes)  

This will create a package.json file for us, so we just need to include it in our Visual Studio project. Now that we have that we can add the libraries we'll need. I'm not showing the command line calls but we want to add these dependencies (as of Angular 2.0.0-beta.14):

{
  ...other stuff...

  "dependencies": {
    "angular2": "^2.0.0-beta.14",
    "es6-shim": "^0.35.0",
    "reflect-metadata": "^0.1.2",
    "rxjs": "^5.0.0-beta.2",
    "zone.js": "^0.6.6"
  },
  "devDependencies": {
    "ts-loader": "^0.8.1",
    "typescript": "^1.8.9",
    "typings": "^0.7.12",
    "webpack": "^1.12.14"
  }
}

With these packages in place we're ready to move ahead.

tsconfig.json

The tsconfig.json file is very important, and drives how our tools (both Visual Studio & webpack) will treat our typescript files. The Typescript docs contain a really nice explanation of how the file works, so I won't repeat it too much here. I will say though that it's very important to have those last two compiler options, emitDecoratorMetadata and experimentalDecorators, otherwise Angular 2 will not work properly.

Side note, the reason I found this is I was creating an Angular 2 service that had all the proper decorators on it, but I couldn't inject it, no matter what I tried. The reason was the emitDecoratorMetadata option was missing from tsconfig. It took me a little bit to figure that one out... :

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "files": [
    "App/main.ts"
  ]
}

That files section is also important because it seems to drive how Visual Studio treats your files when working with them. I typically just include the top-level file, and then Visual Studio understands the typescript module imports, and as a result intellisense works just like it should.

typings

For me, the fact we have something like typings is a necessary evil of moving forward with types in javascript. I have spent most of my recent career working with typed languages, so working without them proves to be error prone, and sometimes just maddening. Luckily, since the Angular team chose to write the library in Typescript, most of the type information is readily available. However, we still need some additional types to get everything Angular needs to compile without any warnings.

In our example, we only need to add ambient typings for a single library - es6-shim. To do that set up typings according to the docs, and run this command:

> typings install --ambient --save es6-shim

That should install the type files, and then you just need to update the top-level file for your app to include them like so:

/// <reference path="../typings/browser.d.ts"/>

// Polyfills
import "es6-shim";  
import "reflect-metadata";  
import "zone.js/dist/zone";  
...rest of the file...

Now you should have the type information needed. As your project grows, simply add the types you may need using typings.

webpack

The last piece we need to pull all of this together (feeling fatigued yet?), is the config file for webpack. Webpack is a very comprehensive tool, but we're trying to keep this as simple as possible, so our config file is just the following:

module.exports = {  
    // Define the entry points for our application so webpack knows what to 
    //  use as inputs
    //
    entry: {
        app: ["./App/main"]
    },

    // Define where the resulting file should go
    //
    output: {
        filename: "assets/[name].bundle.js"
    },

    resolve: {
        extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
    },

    // Turn on source maps for all applicable files.
    //
    devtool: "source-map",

    module: {
        loaders: [
            // Process any typescript or typescript-jsx files using the ts-loader
            //
            {
                test: /\.tsx?$/,
                loaders: ["ts-loader"]
            }
        ]
    }
}

In our case we're not processing any Sass, not performing any minification, not using common chunks...we're just compiling the typescript files into a single js file that we can serve locally. Using webpack here is probably overkill, but as your project grows I think you'll find it's a very useful tool.

With this file in place now we can run webpack from the command line and you should see this output:

That shows our files compiled, we have a map file, and all without any compilation errors. Now we just need to update our Nancy view to use this new bundle instead of all the CDN/SystemJS links:

<!DOCTYPE html>  
<html>  
<head>  
    <title>Angular 2 QuickStart</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

</head>  
<body>  
    <my-app>Loading...</my-app>

    <script src="~assets/app.bundle.js"></script>

</body>  
</html>  

Here's what our solution looks like now:

So at this point we actually have what we need to compile the typescript, and self-host it all. I have used the webpack --watch feature to just have webpack continually watch my files and re-compile when anything changes. That works pretty well when you have a traditional website project, but with a windows service we need to have our updated files copied to the build directory for them to be available. That requires a different approach.

What I really want is to have Visual Studio re-compile my typescript, using webpack, every time I build the solution. Luckily there's an option available to make that possible without much hassle.

Visual Studio, npm & webpack

To help automate the webpack commands we'll leverage the npm scripts feature to create simple scripts to run the webpack commands. This works nicely for our simple project, but many build systems understand npm, so if you ever move to a CI system you won't need to make too many changes. Our scripts are very simple in this case, and just match what we ran on the command line earlier. Here's the updated portion of packages.json:

{
  "name": "service.website",
  "version": "1.0.0",
  "description": "An example that hosts Angular 2 inside a Windows service",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "build:watch": "webpack --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ...rest of file...
}

So we've added two simple scripts - build and build:watch that just trigger webpack commands.

Recent versions of Visual Studio include something called the Task Runner, and it was Microsoft's answer to the increased use of javascript build automation via Grunt & Gulp. However, there is an extension that makes it work with npm too, and that's what we'll use here.

Once we add in the npm runner extension, we should see our npm scripts show up in the task runner explorer:

As noted in the image we also added a binding for our build script so that every time we build the service in Visual Studio, webpack is also run to compile all of our website files. This ensures the updated bundles are ready to go when the build copies them to the output directory. If you build the project now you'll even see the output from webpack right in Visual Studio:

Nice!

Wrapping up

In this post I completed my 2-part walk through of building an Angular 2 application and self-hosting it inside of a Windows service. We updated our original post by using webpack, npm & a Visual Studio extension to compile our typescript into a single bundle every time we build the entire solution. Again, the code for this entire example is available in my GitHub repo:

https://github.com/sstorie/experiments/tree/master/topshelf-angular2-service-webpack

As always, please let me know if you have any feedback in the comments!

comments powered by Disqus