Building a large scale JavaScript application 
in TypeScript

João Moreno
joao.moreno@microsoft.com

What do we build?

Web Standards based
Developer Tools and
Cloud Services

rise4fun

Visual Studio Online "Monaco"

Large JavaScript application

90% client side code

10% server side code

The Road to Monaco

50 kLOC 100 kLOC 500 kLOC
Modules
Classes
Interfaces
Promises
AMD
Lazy Loading
Contributions
Components
Dependency Injection
10% TypeScript 50% TypeScript 100% TypeScript

today Autumn 2011

We enjoy programming in JavaScript

Pains

Large code bases need to come up with compensating patterns for classes, modules and namespaces.

Refactoring JavaScript code is difficult.

"Javascript code rots over time." — Frustrated Developer

Describing APIs means keeping documentation sychronized with the implementation.

TypeScript to the rescue

All JavaScript code is TypeScript.

JavaScript libraries work with TypeScript.


function Greeter(greeting) {
    this.greeting = greeting;
}

Greeter.prototype.greet = function() {
    return "Hello, " + this.greeting;
};
				

TypeScript to the rescue

Structural typing, type inference.

Zero run time cost — Types disappear at run time.


var person = {
	name: 'João',
	male: true,
	country: 'Portugal'
};

// error
var lastName = person.lastName;

// isMale is of type boolean
var isMale = person.male;
				

TypeScript to the rescue

Compiles to idiomatic JavaScript.

Runs everywhere.


// DOM access

var button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = () => alert('Hello!');
				

// Node.JS Express server

var express = require('express');
var app = express();

app.get('/', (req, res) => res.send('Hello!'));
app.listen(3000);
				

Demo

The Road to Monaco

50 kLOC 100 kLOC 500 kLOC
Modules
Classes
Interfaces
Promises
AMD
Lazy Loading
Contributions
Components
Dependency Injection
10% TypeScript 50% TypeScript 100% TypeScript

today Autumn 2011

Code Organization

Namespaces were global object bags.


var Monaco = {};
Monaco.Strings = {};
Monaco.Strings.Util = {};
Monaco.Strings.Util.trim = function () { /* etc */ };
				

No relationship to the source files on disk.

Renaming files, refactoring was a huge pain.

Cycling dependencies were easily unnoticed.

Dependency Management

"… our dependency graph was such a mess that each area had a dependency on just about every other area."
— Embarrassed Developer

Script Order

Eager Script Loading

AMD

Asynchronous Module Definition


define('module_id', ['dependency_id'], function(dependency) {​
	// code
	
	return {​
		// exports
	};
});
				

Popularized by RequireJS.

TypeScript External Modules

TypeScript supports external modules, from which it can generate either CommonJS or AMD modules.


import dependency = require('dependency_id');

export function foo() {
	// code
}
				

While sharing code between CommonJS and AMD is hard by nature, TypeScript makes this a breeze.

Demo

Post AMD

"It feels like a fresh shower. 
Self contained modules, no more cycles, no more globals, clean file system structure."
— Happy Developer

CSS Dependencies

Managing CSS files was as big of a pain as it was managing JavaScript files.

Virtual every CSS file is related to some AMD module.

We implemented an AMD loader plugin that allows us to specify a CSS file as a module dependency.

TypeScript supports this via a pragma comment.


/// <amd-dependency path="vs/css!./hover" />

define(['vs/css!./hover', ... ], function (...) { ... });
				

Lazy Loading

AMD allows us to lazy load modules.


// vs/languages/csharp.contribution

modesExtensions.registerMode(
	'vs.languages.csharp',  // mode name
	['text/x-csharp'],      // mimetypes
	'vs/languages/csharp'   // mode implementation
	'CSMode'                // mode class
);
				

// vs/languages/csharp

export class CSMode {
	// heavy stuff here
}
				

Bundles

AMD allows us to optimize both the number of server requests and the size of each request.

We can bundle modules together.

The Road to Monaco

50 kLOC 100 kLOC 500 kLOC
Modules
Classes
Interfaces
Promises
AMD
Lazy Loading
Contributions
Components
Dependency Injection
10% TypeScript 50% TypeScript 100% TypeScript

today Autumn 2011

Towards 100% TypeScript

Migration out of developer will.

It's mainly code clean-up but sometimes it is real work. We estimated a speed of 300 LOC/hour.

We established team specific rules for the migration, such as no implicit any types or no missing return statements.

Hindsight Quotes

"In JavaScript, you really are at the mercy of your ability to spell."

delete this.markers[range.statMarkerId];
// or is it startMarkerId?
				
"Soon enough, I realized how inconsistent I was. The same data was flowing around in at least 3 different formats!"
— Enlightened Developer

Components

We distribute our modules by compiling to a set of JavaScript files and generating a .d.ts file.

.d.ts files allow to describe API.


/**
 * A selection in the editor
 */
export interface IEditorSelection extends ISelection {
	/**
	 * Test if equals other selection
	 */
	equalsSelection(other: ISelection): boolean;
	
	// ...
}
					

Services, Dependency Injection

We came up with our own services and dependency injection framework to help with decoupling.


export interface IMarkerService {
	change(owner: string, resource: URI, markers: IMarker[]): void;
}

class MarkerNavigationAction {
	constructor(ctx: Services.IPlatformServices) {
		this.markerService = ctx.markerService;
	}
}
					

TypeScript Retrospective

We were always on the bleeding edge...

... but would always do it again.

The benefits clearly outweight the pains.

  • confidence
  • refactoring agility
  • tooling

Starting a New Project?

  • Start with TypeScript.
  • Use external modules.

Thanks!

http://tinyurl.com/trymonaco

http://www.typescriptlang.org/

https://github.com/joaomoreno/large-scale-typescript


joao.moreno@microsoft.com