Tuesday, 20 January 2015

TypeScript: In Praise of Union Types

(& How to Express Functions in UTs)

Have you heard the good news my friend? I refer, of course, to the shipping of TypeScript 1.4 and my favourite language feature since generics.... Union Types.

In the 1.4 announcement Jonathan Turner described Union Types thusly:

JavaScript functions may take a number of possible argument types. Up to now, we’ve supported this using function overloads. Starting with TypeScript 1.4, we’ve generalized this capability and now allow you to specify that that a value is one of a number of different types using a union type:

function f(x: number | number[]) {
  if (typeof x === "number") {
    return x + 10;
  }
  else {
    // return sum of numbers
  }
}

Once you have a value of a union type, you can use a typeof and instanceof checks to use the value in a type-safe way. You'll notice we use this in the above example and can treat x as a number type inside of the if-block.

Union types are a new kind of type and work any place you specify a type.

Lovely right? But what's missing? Well, to my mind, the most helpful aspect of Union Types. Definition file creation.

A little history

That's right - the days before Union Types are now "history" :-)

When creating definition files (*.d.ts) in the past there was a problem with TypeScript. A limitation. JavaScript often relies on "option bags" to pass configuration into a method. An "option bag" is essentially a JavaScript object literal which contains properties which are used to perform configuration. A good example of this is the route parameter passed into Angular's ngRoute when method.

I'd like to draw your attention to 2 of the properties that can be passed in (quoted from the documentation):

  • controller – {(string|function()=} – Controller fn that should be associated with newly created scope or the name of a registered controller if passed as a string.
  • template – {string=|function()=} – html template as a string or a function that returns an html template as a string which should be used by ngView or ngInclude directives. This property takes precedence over templateUrl.

    If template is a function, it will be called with the following parameters:

    {Array.<Object>} - route parameters extracted from the current $location.path() by applying the current route

Both of these properties can be of more than 1 type.

  • controller can be a string or a function.
  • template can be a string or a function that returns a string and has $routeParams as a parameter.

There's the rub. Whilst it was possible to overload functions in TypeScript pre 1.4, it was not possible to overload interface members. This meant the only way to model these sorts of properties was by seeking out a best common type which would fit all scenarios. This invariably meant using the any type. Whilst that worked it didn't lend any consuming code a great deal of type safety. Let's look at a truncated version of angular-route.d.ts for these properties prior to union types:

declare module ng.route {

  // ... 
 
  interface IRoute {
 
    /**
     * {(string|function()=}
     * Controller fn that should be associated with newly created scope or 
     * the name of a registered controller if passed as a string.
     */
    controller?: any;

    /**
     * {string=|function()=}
     * Html template as a string or a function that returns an html template 
     * as a string which should be used by ngView or ngInclude directives. This 
     * property takes precedence over templateUrl.
     * 
     * If template is a function, it will be called with the following parameters:
     * 
     * {Array.<Object>} - route parameters extracted from the current 
     * $location.path() by applying the current route
     */
    template?: any;

    // ... 
  }
 
  // ... 
}

It's any city... Kind of sticks in the craw doesn't it?

A new dawn

TypeScript 1.4 has shipped and Union Types are with us. We can do better than any. So what does angular-route.d.ts look like now we have Union Types?

declare module ng.route {

  // ... 
 
  interface IRoute {
 
    /**
     * {(string|function()=}
     * Controller fn that should be associated with newly created scope or 
     * the name of a registered controller if passed as a string.
     */
    controller?: string|Function;

    /**
     * {string=|function()=}
     * Html template as a string or a function that returns an html template 
     * as a string which should be used by ngView or ngInclude directives. This 
     * property takes precedence over templateUrl.
     * 
     * If template is a function, it will be called with the following parameters:
     * 
     * {Array.<Object>} - route parameters extracted from the current 
     * $location.path() by applying the current route
     */
    template?: string | { ($routeParams?: ng.route.IRouteParamsService) : string; }

    // ... 
  }
 
  // ... 
}

With these changes in place we are now accurately modelling the route option bags in TypeScript. Hoorah!!!

Let's dig in a little. If you look at the controller definition it's pretty straightforward. string|Function - clearly the controller can be a string or a Function. Simple.

Now let's look at the template definition by itself:

    template?: string | { ($routeParams?: ng.route.IRouteParamsService) : string; }

As with the controller the template can be a string - that is pretty clear. But what's that hovering on the other side of the "|"? What could { ($routeParams?: ng.route.IRouteParamsService) : string; } be exactly?

Well, in a word, it's a Function. The controller would allow any kind of function at all. However the template definition is deliberately more restrictive. This defines a function which must return a string and which receives an optional parameter of $routeParams of type ng.route.IRouteParamsService.

State of the Union

Hopefully you can now see just how useful Union Types are and how you can express specific sorts of function definitions as part of a Union Type.

The thing that prompted me first to write this post was seeing that there don't appear to be any examples out there of how to express functions inside Union Types. I only landed on the syntax myself after a little experimentation in Visual Studio after I'd installed TS 1.4. I've started work on bringing Union Types to the typings inside DefinitelyTyped and so you'll start to see them appearing more and more. But since it's rather "hidden knowledge" at present I wanted to do my bit to make it a little better known.

As Daniel helpfully points out in the comments there is an alternate syntax - lambda style. So instead of this:

    template?: string | { ($routeParams?: ng.route.IRouteParamsService) : string; }

You could write this:

    template?: string | (($routeParams?: ng.route.IRouteParamsService) => string);

Just remember to place parentheses around the lambda to clearly delineate it.

Bonfire of the Overloads

Before I sign off I should mention the ability Union Types give you to define a much terser definition file. Basically the "|" operator makes for a bonfire of the overloads. Where you previously may have had 6 overloads for the same method (each with identical JSDoc) you now only need 1. Which is beautiful (and DRY).

It's surprising just what a difference it makes. This is jQuery.d.ts last week (pre TypeScript 1.4). This is jQuery.d.ts now - with Union Types aplenty. Last week it was ~4000 lines of code. This week it's ~3200 lines of code. With the same functionality. Union Types are FANTASTIC!

Wednesday, 7 January 2015

Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 2

"Automation, automation, automation." Those were and are Tony Blair's priorities for keeping open source projects well maintained.

OK, that's not quite true... But what is certainly true is that maintaining an open source project takes time. And there's only so much free time that anyone has. For that reason, wherever you can it makes sense to AUTOMATE!

Last time we looked at how you can take an essentially static ASP.Net MVC site (in this case my jVUNDemo documentation site) and generate an entirely static version using Wget. This static site has been pushed to GitHub Pages and is serving as the documentation for jQuery Validation Unobtrusive Native (and for bonus points is costing me no money at all).

So what next? Well, automation clearly! If I make a change to jQuery Validation Unobtrusive Native then AppVeyor already bounds in and performs a continuous integration build for me. It picks up the latest source from GitHub, pulls in my dependencies, performs a build and runs my tests. Lovely.

So the obvious thing to do is to take this process and plug in the generation of my static site and the publication thereof to GitHub pages. The minute a change is made to my project the documentation should be updated without me having to break sweat. That's the goal.

GitHub Personal Access Token

In order to complete our chosen mission we're going to need a GitHub Personal Access Token. We're going to use it when we clone, update and push our GitHub Pages branch. To get one we biff over to Settings / Applications in GitHub and click the "Generate New Token" button.

The token I'm using for my project has the following scopes selected:

appveyor.yml

With our token in hand we turn our attention to AppVeyor build configuration. This is possible using a file called appveyor.yml stored in the root of your repo. You can also use the AppVeyor web UI to do this. However, for the purposes of ease of demonstration I'm using the file approach. The jQuery Validation Unobtrusive Native appveyor.yml looks like this:

#---------------------------------#
#      general configuration      #
#---------------------------------#

# version format
version: 1.0.{build}

#---------------------------------#
#    environment configuration    #
#---------------------------------#

# environment variables
environment:
  GithubEmail: [email protected]
  GithubUsername: johnnyreilly
  GithubPersonalAccessToken:
    secure: T4M/N+e/baksVoeWoYKPWIpfahOsiSFw/+Zc81VuThZmWEqmrRtgEHUyin0vCWhl    

branches:
  only:
    - master

install:
- ps: choco install wget

build:
  verbosity: minimal

after_test:
- ps: ./makeStatic.ps1 $env:APPVEYOR_BUILD_FOLDER
- ps: ./pushStatic.ps1 $env:APPVEYOR_BUILD_FOLDER $env:GithubEmail $env:GithubUsername $env:GithubPersonalAccessToken

There's a number of things you should notice from the yml file:

  • We create 3 environment variables: GithubEmail, GithubUsername and GithubPersonalAccessToken (more on this in a moment).
  • We only build the master branch.
  • We use Chocolatey to install Wget which is used by the makeStatic.ps1 Powershell script.
  • After the tests have completed we run 2 Powershell scripts. First makeStatic.ps1 which builds the static version of our site. This is the exact same script we discussed in the previous post - we're just passing it the build folder this time (one of AppVeyor's environment variables). Second, we run pushStatic.ps1 which publishes the static site to GitHub Pages.

We pass 4 arguments to pushStatic.ps1: the build folder, my email address, my username and my personal access token. For the sake of security the GithubPersonalAccessToken has been encrypted as indicated by the secure keyword. This is a capability available in AppVeyor here.

This allows me to mask my personal access token rather than have it available as free text for anyone to grab.

pushStatic.ps1

Finally we can turn our attention to how our Powershell script pushStatic.ps1 goes about pushing our changes up to GitHub Pages:

param([string]$buildFolder, [string]$email, [string]$username, [string]$personalAccessToken)

Write-Host "- Set config settings...."
git config --global user.email $email
git config --global user.name $username
git config --global push.default matching

Write-Host "- Clone gh-pages branch...."
cd "$($buildFolder)\..\"
mkdir gh-pages
git clone --quiet --branch=gh-pages https://$($username):$($personalAccessToken)@github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native.git .\gh-pages\
cd gh-pages
git status

Write-Host "- Clean gh-pages folder...."
Get-ChildItem -Attributes !r | Remove-Item -Recurse -Force

Write-Host "- Copy contents of static-site folder into gh-pages folder...."
copy-item -path ..\static-site\* -Destination $pwd.Path -Recurse

git status
$thereAreChanges = git status | select-string -pattern "Changes not staged for commit:","Untracked files:" -simplematch
if ($thereAreChanges -ne $null) { 
    Write-host "- Committing changes to documentation..."
    git add --all
    git status
    git commit -m "skip ci - static site regeneration"
    git status
    Write-Host "- Push it...."
    git push --quiet
    Write-Host "- Pushed it good!"
} 
else { 
    write-host "- No changes to documentation to commit"
}

So what's happening here? Let's break it down:

  • Git is configured with the passed in username and email address.
  • A folder is created that sits alongside the build folder called "gh-pages".
  • We clone the "gh-pages" branch of jQuery Validation Unobtrusive Native into our "gh-pages" directory. You'll notice that we are using our GitHub personal access token in the clone URL itself.
  • We delete the contents of the "gh-pages" directory leaving it empty.
  • We copy across the contents of the "static-site" folder (created by makeStatic.ps1) into the "gh-pages".
  • We use git status to check if there are any changes. (This method is completely effective but a little crude to my mind - there's probably better approaches to this.... shout me in the comments if you have a suggestion.)
  • If we have no changes then we do nothing.
  • If we have changes then we stage them, commit them and push them to GitHub Pages. Then we sign off with an allusion to 80's East Coast hip-hop... 'Cos that's how we roll.

With this in place, any changes to the docs will be automatically published out to our "gh-pages" branch. Our documentation will always be up to date thanks to the goodness of AppVeyor's Continuous Integration service.