View
574
Download
2
Category
Preview:
Citation preview
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?
Recommended