TypeScript

JavaScript that scales

Ali Arafati

@CitizenOfTheWeb

ali.arafati@axis.com
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

JavaScript

We Bare Bears

JavaScript Evolution;

function getTallPeopleCount(people) {
  var count;

  for (var i = 0; i < people.length; i++) {
    if (people.height >= 180) {
      count++;
    }
  }

  return count;
}
function getTallPeopleCount(people) {
  return people.filter(function (person) {
    return person.height >= 180;
  }).length;
}
function getTallPeopleCount(people) {
  return people.filter(person => person.height >= 180).length;
}

ECMAScript 3 - 1999

ECMAScript 5 - 2009

ECMAScript 6 - 2015

JavaScript Evolution: backward compatible

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun/*, thisArg*/) {
    'use strict';
    if (this === void 0 || this === null) {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) {
        var val = t[i];

        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }

    return res;
  };
}
function getTallPeopleCount(people) {
  return people.filter(function(person) {
    return person.height >= 180;
  }).length;
}

Polyfill (or shim)

Compatibility table for ES5: http://kangax.github.io/compat-table/es5/

ECMAScript 6:

  • Promise
  • Map & Set
  • Symbols (~)
  • Prototypes
    • Number
    • String
    • Array

1

JavaScript Evolution: backward compatible

Syntatic features

function getTallPeopleCount(people) {
  return people.filter(person => person.height >= 180).length;
}
function getTallPeopleCount(people) {
  return people.filter(function (person) {
    return person.height >= 180;
  }).length;
}

Transpilers (i.e. compilers)

2

JavaScript Evolution: backward compatible

ECMAScript 5 features (getter/setter)

var object = {
  get a() { return object._a; }
  set a(value) { object._a = value; }
};

ECMAScript 6 features (proxies)

var handler = {
    get: function(target, name) {
        return name in target ?
            target[name] :
            37;
    }
};

var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37

IE8 and lower 😞

3

no ES5 support 😞

no ES3 support

JavaScript - State of the art

  • ECMAScript features
  • Check compatibility
    • things you can use out of the box
    • things you need shim/polyfills for
    • things you can transpile
    • things you cannot use in the target platform
  • Use a transpiler
  • Use shim/polyfills

JavaScript Feature Gap

ES3

ES5

ES 2015 - ES6

ES 2016

ES 2017

ES 2018

ES 2019

1999

2009

state of the art JavaScript

NodeJS

browsers

legacy

Smart phones

Node 6.5

Tooling is important

Mistakes;

image source: http://www.lexolutionit.com/article_details.php?id=337

  • Cosmetic
  • Typos
  • Deprecate API calls
  • Dangerous API calls
  • Bad practices
const bad = new String('WAT?');
const areWeGood = !!condition;
const howFar = ~bad.indexOf('?');

const cats = localStorage.getItem(STORAGE_KEY);

const result = eval('2 + 12');

setTimeout('function() { console.log(1) }');

setInterval(() => { checkTheWeather(); }, 1000);
// invalid
$rootScope.$on('destroy', function () {
    // ...
}); // error: You probably misspelled $on("$destroy").
// valid
angular.module('myModule').controller('MyController', function () {
   // ... MyCtrl or MyControler or Mycontroller is not valid
});
// invalid
$rootScope.$on('event', function () {
    // ...
}); // error: The "$on" call should be assigned to a variable,
    // in order to be destroyed during the $destroy event

ESLint

function getTallPeopleCount(people) {
  return people.filter(person => persom.height >= 180).length;
}
function getTallPeopleCount(people) {
  return people.filter(person => person.hieght >= 180).lenght;
}
function sortPeopleByName(people) {
  return people.sort(function(p1, p2) {
    p1.name.localCompare(p2.name);
  });
}

1

2

Mistakes;

function getGamePlayersAsync() {
  return Game.waitForConnection(connection => {
    return connection.players;
  });
}

3

function getGamePlayersAsync() {
  return Game.waitForConnection(connection => {
    console.log(connection);
    return connection.players;
  });
}
function getTallPeopleCount(people) {
  return people.filter(person => persom.height >= 180).length;
}

1

DEMO;

TypeScript:

JavaScript that scales

A statically typed superset of JavaScript that compiles to plain JavaScript.

If it looks like a duck and quacks like a duck, it's a duck

// person.ts

interface Person {
  name: string;
}

function getName(person: Person) {
  return person.name;
}
// index.ts

let employee = {
  name: 'Ali',
  company: 'Axis'
};

getName(employee);  // OK
getName({ name: 'Donald Duck' }); // OK
getName({ name: 13 }); // ERROR
getName({ blah: 'yada' }); // ERROR

let movie = getMovieByName('Fantastic Beasts and Where to Find Them');
getName(movie); // OK

Demo;

Control Flow Base Type Analysis

With TypeScript 2.0, the type checker analyses all possible flows of control in statements and expressions to produce the most specific type possible (the narrowed type) at any given location for a local variable or parameter that is declared to have a union type.

function normalize(id: string | number) {
  if (typeof id === 'number') {
    return id;
  }

  return +id.split(':')[0];
}

normalize(1234);
normalize('1234:xp:12')

Type: Any

let k;

k = '';
k.substr(1);

k = 12.34;
k.toFixed();
function f1() {
    let x = [];

    x.push(5);

    if (cond()) {
      x[1] = "hello";
    }

    x.unshift(true);

    return x;
}

StrictNullCheck

string

"foo"

""

undefined

null

number

42

-3.1415

undefined

null

NaN

boolean

true

false

undefined

null

StrictNullCheck (demo)

string

"foo"

""

undefined

null

number

42

-3.1415

NaN

boolean

true

false

Literal Types

var r = validateSomething();

if (r.success === true) {
  r.data;
} else {
  r.errorNo;
  r.message;
}
type Result<T> =
  { success: true; data: T } |
  { success: false; errorNo: number; message: string; };

function validateSomething(): Result<number>;
const KEY = '[SECURE KEY]';
const PI = 3.14;
const flag = true;

const x = cond() ? PI : '3.1415';

let bit: 1 | 0;

let env: 'production' | 'development';

Tagged union types

interface UserRoot {
  type: 'UNIL' | 'LDAP';
}

interface LDAP extends UserRoot {
  type: 'LDAP';
  username: string;
  name: string;
  groups: string[];
  roles: string;
}

interface UNIL extends UserRoot {
  type: 'UNIL';
  username: string | number;
  hid: string;
}

type User = LDAP | UNIL;
interface User {
  type: 'UNIL' | 'LDAP'; // or string
  username: string | number;
  hid?: string;    // note the tentetive type
  name?: string;
  groups?: string[];
  roles?: string;
}

Tagged union types

document.querySelector('script').src; // OK

document.querySelector('a').href; // OK
interface NodeSelector {
    querySelector(selectors: 'a'): ElementAnchorElement | null;
    querySelector(selectors: 'script'): ElementScriptElement | null;
    querySelector(selectors: string): Element | null;
    // ...
}

Tagged union types

interface AddToCartAction {
  type: 'ADD_TO_CART';
  product: {
    id: number;
    name: string;
  };
}

interface RemoveFromCartAction {
  type: 'REMOVE_FROM_CART';
  productId: number;
}

type ShoppingCartActions = AddToCartAction | RemoveFromCartAction;
export default (state = initialState, action: ShoppingCartActions) => {
  switch (action.type) {
    case ADD_TO_CART:
      // action is AddToCartAction
      break;
    case REMOVE_FROM_CART:
      // action is RemoveFromCartAction
      break;
    default: return state;
  }
}

Write less; do more;

  • async/await

  • decorators

async/await

function getUser() {
  return request('user').then(user => {
    if (user.hasProjects) {
      return request('projects').then(projects => {
        return {
          ...user,
          projects
        };
      });
    }
    return user;
  });
}
getUser().then(user => {
  if (user.hasProjects) {
    user.projects; // OK
  } else {
    user.projects; // Empty, or maybe undefined
  }
});

async/await

function getUser() {
  return request('user').then(user => {
    if (user.hasProjects) {
      return request('projects').then(projects => {
        return {
          ...user,
          projects
        };
      });
    }
    return user;
  });
}
async function getUser() {
  let user = await request('user');
  if (user.hasProjects) {
    user.projects = await request('projects');
  }
  return user;
}

Decorators

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

import { throttle } from 'core-decorators';

class Editor {

  content = '';

  @throttle(500, { leading: false })
  updateContent(content) {
    this.content = content;
  }
}
function throttle(timeout, config) {
  return function(target, methodName) {
    const originalMethod = target[methodName];
    let h;

    target.defineProperty(target, methodName, {
      value: function(...args) {
        if (h) { clearTimeout(h); }

        h = setTimeout(() => {
          originalMethod.apply(this, args);
        }, timeout);
      }
    });
  }
}
class Post {
  public id: number;
  
  public title: string;
  public content: string;

  @JSON('writters.author')
  public author: string;

  @JSON('writters.editor')
  public editor: string;
}
// Post json structure

{
  id: 1,
  title: 'foo',
  content: 'bar',
  writters: {
    author: 'buzz',
    editor: 'bizz'
  }
}
var Post = (function () {
    function Post() {
    }
    return Post;
}());
__decorate([
    JSON('writters.author'),
    __metadata("design:type", String)
], Post.prototype, "author");
__decorate([
    JSON('writters.editor'),
    __metadata("design:type", String)
], Post.prototype, "editor");

emitDecoratorMetadata

@API({
  endpoint: '/posts/:id',
  canUpdate: (user, post) => user.canPost(post),
  canDelete: (user, post) => user.canDelete(post)
})
class Post {
  public id: number;
  
  public title: string;
  public content: string;

  @JSON('writters.author')
  public author: string;

  @JSON('writters.editor')
  public editor: string;

  @API({
    endpoint: (post) => `/posts/${post.id}/comments`
  })
  @JSON('user_comments')
  public comments: Comment[];
}

Demo

Angular 1.x

NodeJS

React

When/Why TypeScript

  • Large JavaScript codebase
  • Large Teams
  • Fully compatible/adaptable with EcmaScript Next
  • Types are optional - gradual move - partial
  • Easy refactoring
  • Debug natively - Sourcemap
  • Open Source - Active community
  • Toolings (IDE - LINTER - Language Service)
  • Already available typings for third party libs and frameworks
  • Typings can give you great insights on the code
  • Typings can provide great documentation for the code
  • Share typings in universal applications

Who is using TypeScript

  • Google
    • Angular 2 (Angular 4, ...)
  • RxJS, Ionic, Cycle.js, Blueprint, Dojo, NativeScript, Plottable
  • Microsoft
    • Visual Studio Code

Questions?

Ali Arafati

@CitizenOfTheWeb

https://slides.com/aliai/typescript

TypeScript: JavaScript that scales

By Ali Arafati

TypeScript: JavaScript that scales

There are always certain challenges when it comes to staying productive in a large JavaScript codebase. It becomes more difficult to see how things are connected, while introducing changes may bring about more bugs and pain than it actually contributes to the system. Giant leading companies have tried to address this fact by effortlessly working on various toolings: Google with Closure Compiler, Facebook with Flow and Microsoft with TypeScript. While all these tools aim at a similar premise, in my opinion, TypeScript is ahead in this specific game by far. TypeScript is about making JavaScript scale. It doesn't aim at replacing JavaScript and certainly not changing the way we love and write in the language. TypeScript is where you can optionally define static types while staying in the current state of the art JavaScript, and adhere to them as your principles. It's an open-source project with an active community, and it also offers great toolings cross-platform.

  • 1,067