how we migrated our huge angularjs app from coffeescript to typescript

Post on 15-Jul-2015

574 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

HOW WE MIGRATED OUR HUGE ANGULAR.JSAPP FROM COFFEESCRIPT TO TYPESCRIPT

Luka Zakrajšek

CTO @ Koofr

@bancek

Ljubljana Spring-ish JavaScript Meetup

February 10, 2015

ABOUT MEFRI graduateCTO and cofounder at Koofr

frontend, backend, iOS, Windows Phonealmost 3 years of Angular.js

KOOFRKoofr is white-label cloud storage solution for ISPs

ANGULAR.JS AT KOOFRweb app

desktop application(web app wrapped into browser to look like native)

HUGE APP?26 route controllers90 directives22 factories14 filters15 services

6012 LOC of Coffee (30 files)

2937 LOC of Angular HTML templates (101 files)

WE WERE HAPPY WITH COFFEESCRIPTPros

clean codeclasses

Cons

technical debtfear of refactoringnot enough tests

COFFEESCRIPTLaunched in 2010.

Gained traction with Rails support in 2011

$(function() { // Initialization code goes here})

$ -> # Initialization code goes here

BROWSERIFYLets you require('modules') in the browser by bundling up all of your

dependencies.

var unique = require('uniq');var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];console.log(unique(data));

$ npm install uniq$ browserify main.js -o bundle.js

<script src="bundle.js"></script>

TYPESCRIPTa typed superset of JavaScript that compiles to plainJavaScript

any existing JavaScript program is also valid TypeScriptprogram

developed by Microsoft

from lead architect of C# and creator of Delphi and TurboPascal

FIND A TYPO

class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } getDist() { return Math.sqrt(this.x * this.x + this.y * this.y); }}

var p = new Point(3,4);var dist = p.getDst();alert("Hypotenuse is: " + dist);

FEATURES

JAVASCRIPT

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

// Oops, we're passing an object when we want a string.var greeter = new Greeter({message: "world"});

alert(greeter.greet());

TYPES

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

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

var greeter = new Greeter("world");

alert(greeter.greet());

CLASSES

class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; }}

var greeter = new Greeter("world");

alert(greeter.greet());

TYPES

class Animal { constructor(public name: string) { } move(meters: number) { alert(this.name + " moved " + meters + "m."); }}

class Snake extends Animal { constructor(name: string) { super(name); } move() { alert("Slithering..."); super.move(5); }}

var sam: Animal = new Snake("Sammy the Python");sam.move();

GENERICS

class Greeter<T> { greeting: T; constructor(message: T) { this.greeting = message; } greet() { return this.greeting; }}

var greeter = new Greeter<string>("Hello, world");alert(greeter.greet());

MODULES

module Sayings { export class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } }}var greeter = new Sayings.Greeter("world");alert(greeter.greet());

USAGE

npm install -g typescript

tsc helloworld.ts

<script src="helloworld.js"></script>

Or Grunt, Gulp ...

TOOLSincluded in Visual StudioJetBrains (IntelliJ)VimEmacsSublime TextCATS

MIGRATIONcoffee-script-to-typescript to the rescue

npm install -g coffee-script-to-typescritpt

coffee-to-typescript -cma your/files/*.coffee

EXISTING LIBRARIESwe use more than 30 libraries

to be completely type-safe,you need definitions for all external libraries

DefinitelyTyped - more than 700 librarieshttps://github.com/borisyankov/DefinitelyTyped

EXAMPLEjgrowl.d.ts

/// <reference path="../jquery/jquery.d.ts" />interface JQueryStatic { jGrowl: jgrowl.JGrowlStatic;}declare module jgrowl { interface JGrowlOptions { sticky?: boolean; position?: string; beforeOpen?: Function; // ... } interface JGrowlStatic { (msg: string, options?: JGrowlOptions): void; }}

$.jGrowl({ sticky: true, beforeOpen: () => { console.log("opening") } })

ANGULARJS - BEFORECoffeeScript

angular.module('comments.controllers', []).directive('comments', -> restrict: 'E' scope: mount: '=' replace: yes templateUrl: 'comments/comments.html' controller: ($scope, Api) -> $scope.comments = []

$scope.load = -> Api.call Api.api.Comments.commentsRange($scope.mount.id, 0, 10) success: (res) -> $scope.comments = res.comments

$scope.load())

ANGULARJS - AFTERTypeScript

/// <reference path="../app.ts" />

module comments {

interface CommentsScope extends ng.IScope { mount: k.Mount comments: Array<k.Comment> load(): void }

export class CommentsCtrl { constructor($scope: CommentsScope, Api: api.Api) { $scope.comments = [];

$scope.load = () => { Api.get(Api.api.Comments.commentsRange($scope.mount.id, 0, .then((res) => { $scope.comments = res.comments; }); } }; } }

export var commentsDirective: ng.IDirectiveFactory = () => { return { restrict: "E", scope: { mount: "=", appendComment: "=" }, replace: true, templateUrl: "comments/comments.html", controller: CommentsCtrl }; };}

PROJECT STRUCTURE

files/comments/utils/...app.tsmain.d.tstypings.d.ts

PROJECT STRUCTUREapp.ts

/// <reference path="main.d.ts" />/// <reference path="files/index.ts" />/// <reference path="comments/index.ts" />/// <reference path="utils/index.ts" />

export var module = angular.module("app", [ "gettext",

files.module.name, comments.module.name, utils.module.name ])}

PROJECT STRUCTUREcomments/index.ts

/// <reference path="../app.ts" />

/// <reference path="commentsDirective.ts" />

module comments { export var module = angular.module("comments", []) .directive("comments", commentsDirective);}

HOW TO TEST EVERYTHING?code coverage to the rescue

usually used for test code coverage

we can use it to see which lines were covered by manually"testing" the app

LIVECOVERNot published yet. Will be on GitHub and npm.

# Generate annotated JavaScript code with Blanket.js$ simple-blanket -o app-cover.js app.js

# Run LiveCover server$ livecover -p 3000

<script src="http://localhost:3000/coverage.js"></script>

Open in your browser and start clicking like crazy.

https://localhost:3000

QUESTIONS?

top related