HOW WE MIGRATED OUR HUGE ANGULAR.JS
APP FROM COFFEESCRIPT TO TYPESCRIPT
Luka Zakrajšek
CTO @ Koofr
@bancek
Ljubljana Spring-ish JavaScript Meetup
February 10, 2015
ABOUT ME
FRI graduate
CTO and cofounder at Koofr
frontend, backend, iOS, Windows Phone
almost 3 years of Angular.js
KOOFR
Koofr is white-label cloud storage solution for ISPs
ANGULAR.JS AT KOOFR
web app
desktop application
(webappwrappedintobrowsertolooklikenative)
HUGE APP?
26 route controllers
90 directives
22 factories
14 filters
15 services
6012 LOC of Coffee (30 files)
2937 LOC of Angular HTML templates (101 files)
WE WERE HAPPY WITH COFFEESCRIPT
Pros
clean code
classes
Cons
technical debt
fear of refactoring
not enough tests
COFFEESCRIPT
Launched in 2010.
Gained traction with Rails support in 2011
$(function(){
//Initializationcodegoeshere
})
$->
#Initializationcodegoeshere
BROWSERIFY
Lets you require('modules') in the browser by bundling up all of your
dependencies.
varunique=require('uniq');
vardata=[1,2,2,3,4,5,5,5,6];
console.log(unique(data));
$npminstalluniq
$browserifymain.js-obundle.js
<scriptsrc="bundle.js"></script>
TYPESCRIPT
a typed superset of JavaScript that compiles to plain
JavaScript
any existing JavaScript program is also valid TypeScript
program
developed by Microsoft
from lead architect of C# and creator of Delphi and Turbo
Pascal
FIND A TYPO
classPoint{
x:number;
y:number;
constructor(x:number,y:number){
this.x=x;
this.y=y;
}
getDist(){
returnMath.sqrt(this.x*this.x+
this.y*this.y);
}
}
varp=newPoint(3,4);
vardist=p.getDst();
alert("Hypotenuseis:"+dist);
FEATURES
JAVASCRIPT
functionGreeter(greeting){
this.greeting=greeting;
}
Greeter.prototype.greet=function(){
return"Hello,"+this.greeting;
}
//Oops,we'repassinganobjectwhenwewantastring.
vargreeter=newGreeter({message:"world"});
alert(greeter.greet());
TYPES
functionGreeter(greeting:string){
this.greeting=greeting;
}
Greeter.prototype.greet=function(){
return"Hello,"+this.greeting;
}
vargreeter=newGreeter("world");
alert(greeter.greet());
CLASSES
classGreeter{
greeting:string;
constructor(message:string){
this.greeting=message;
}
greet(){
return"Hello,"+this.greeting;
}
}
vargreeter=newGreeter("world");
alert(greeter.greet());
TYPES
classAnimal{
constructor(publicname:string){}
move(meters:number){
alert(this.name+"moved"+meters+"m.");
}
}
classSnakeextendsAnimal{
constructor(name:string){super(name);}
move(){
alert("Slithering...");
super.move(5);
}
}
varsam:Animal=newSnake("SammythePython");
sam.move();
GENERICS
classGreeter<T>{
greeting:T;
constructor(message:T){
this.greeting=message;
}
greet(){
returnthis.greeting;
}
}
vargreeter=newGreeter<string>("Hello,world");
alert(greeter.greet());
MODULES
moduleSayings{
exportclassGreeter{
greeting:string;
constructor(message:string){
this.greeting=message;
}
greet(){
return"Hello,"+this.greeting;
}
}
}
vargreeter=newSayings.Greeter("world");
alert(greeter.greet());
USAGE
npminstall-gtypescript
tschelloworld.ts
<scriptsrc="helloworld.js"></script>
Or Grunt, Gulp ...
TOOLS
included in Visual Studio
JetBrains (IntelliJ)
Vim
Emacs
Sublime Text
CATS
MIGRATION
coffee-script-to-typescript to the rescue
npminstall-gcoffee-script-to-typescritpt
coffee-to-typescript-cmayour/files/*.coffee
EXISTING LIBRARIES
we use more than 30 libraries
to be completely type-safe,
you need definitions for all external libraries
DefinitelyTyped - more than 700 libraries
https://github.com/borisyankov/DefinitelyTyped
EXAMPLE
jgrowl.d.ts
///<referencepath="../jquery/jquery.d.ts"/>
interfaceJQueryStatic{
jGrowl:jgrowl.JGrowlStatic;
}
declaremodulejgrowl{
interfaceJGrowlOptions{
sticky?:boolean;
position?:string;
beforeOpen?:Function;
//...
}
interfaceJGrowlStatic{
(msg:string,options?:JGrowlOptions):void;
}
}
$.jGrowl({sticky:true,beforeOpen:()=>{
console.log("opening")}})
ANGULARJS - BEFORE
CoffeeScript
angular.module('comments.controllers',[]).directive('comments',->
restrict:'E'
scope:
mount:'='
replace:yes
templateUrl:'comments/comments.html'
controller:($scope,Api)->
$scope.comments=[]
$scope.load=->
Api.callApi.api.Comments.commentsRange($scope.mount.id,0,10
success:(res)->
$scope.comments=res.comments
$scope.load()
)
ANGULARJS - AFTER
TypeScript
///<referencepath="../app.ts"/>
modulecomments{
interfaceCommentsScopeextendsng.IScope{
mount:k.Mount
comments:Array<k.Comment>
load():void
}
exportclassCommentsCtrl{
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;
});
}
};
}
}
exportvarcommentsDirective:ng.IDirectiveFactory=()=>{
return{
restrict:"E",
scope:{
mount:"=",
appendComment:"="
},
replace:true,
templateUrl:"comments/comments.html",
controller:CommentsCtrl
};
};
}
PROJECT STRUCTURE
files/
comments/
utils/
...
app.ts
main.d.ts
typings.d.ts
PROJECT STRUCTURE
app.ts
///<referencepath="main.d.ts"/>
///<referencepath="files/index.ts"/>
///<referencepath="comments/index.ts"/>
///<referencepath="utils/index.ts"/>
exportvarmodule=angular.module("app",[
"gettext",
files.module.name,
comments.module.name,
utils.module.name
])
}
PROJECT STRUCTURE
comments/index.ts
///<referencepath="../app.ts"/>
///<referencepath="commentsDirective.ts"/>
modulecomments{
exportvarmodule=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
LIVECOVER
Notpublishedyet.WillbeonGitHubandnpm.
#GenerateannotatedJavaScriptcodewithBlanket.js
$simple-blanket-oapp-cover.jsapp.js
#RunLiveCoverserver
$livecover-p3000
<scriptsrc="http://localhost:3000/coverage.js"></script>
Open in your browser
and start clicking like crazy.
https://localhost:3000
QUESTIONS?

How we migrated our huge AngularJS app from CoffeeScript to TypeScript