Skip to content

Instantly share code, notes, and snippets.

@bleucitron
Last active February 22, 2018 04:16
Show Gist options
  • Save bleucitron/383af45ba7edc359cc18 to your computer and use it in GitHub Desktop.
Save bleucitron/383af45ba7edc359cc18 to your computer and use it in GitHub Desktop.

I compiled some tips for Typescript usage, because my transition to this !#@*$ tool has been painful in many ways, especially because i'm a n00b.

However, it might be a good idea to use it in the end.

Install

npm install -g typescript

This will give you access to tsc Typescript compiler.

Basic Usage

You now write only .ts files. You can compile your corresponding .js files by running something like:

tsc my/path/main.ts

This should output your .js files no matter the TS errors you might have done.

There are various options that can be used. The ones we currently use are:

  • --noImplicitAny: TS is now a bitch that will raise an error everytime something's implicitly declared as any
  • --sourceMap: TS generates the source maps for the compiled .ts files. This will allow to have explicit paths to the errors
  • --target ES5: specifies into which ES version the .js files will be written. Default is ES3, but we use ES5
  • --module commonjs: specifies that you can to use modules. We use commonjs, but others are available.
  • -w my/path/**/*.ts: this is for watching all files described by the glob associated. It will generate the corresponding .js files.

Usage with browserify

It is technically possible to use Typescript with browserify. There is the tsify npm package.

From what I could see, the TS errors do make the browserify process crash, which consequently won't generate your .js files. This is problematic, because this won't let you work unless you resolve all TS issues, which, let's be honest, is not humanly possible when you work with non-TS-friendly modules.

I didn't find any simple solution over the internet, so I decided (with a little help from my friend David) to use gulp instead.

Usage with gulp

To be able to work with browserify peacefully, and also to allow multiple watches, you can use the classic gulp solution.

For instance:

  • you watch your .ts files with a TSwatcher which will generate the .js files
  • you watch your client-side .js files with a JSwatcher which will generate your browserify-bundle.js

To use TS with gulp, several packages are available, but gulp-typescript seems excellent.

npm install gulp-typescript --save
npm install gulp-sourcemaps --save-dev // to be able to generate sourcemaps

Then you can write:

var gulp = require('gulp');
var ts = require('gulp-typescript');
var sourcemaps = require('gulp-sourcemaps');

    return gulp.src('my/path/myFile.ts')
        .pipe(sourcemaps.init())
        .pipe(ts({
            noImplicitAny: true,
            target: 'ES5',
            module: 'commonjs'
        }))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('./my/other/path'));

WARNING: do NOT use the out option with your gulp-typescript function, as it is shown in their ReadMe. This is incompatible with module generation, and will make the TS compiler do funny stuff with your file system...

One example is available in the 6bin repository.

Working with Typescript

From my point of view , there are 2 main advantages working with Typescript: type check, and ES6 syntax.

The main problem being that it might take you a big while until you get to work fluently again..., even if TS is supposed to issue your .js files whatever happens (unless you tell him not to).

There are some tutorials here and there, but here are some n00b insights.

Types

Type verification is the core intention of Typescript. It will issue errors when variables with the wrong type are used, preventing you from doing crazy shenanigans.

You can declare your variables like this:

var myVariable: string = 'je suis une variable en français';

The basic types you can use are:

  • boolean
  • number
  • string
  • array
  • any, this is when you don't care/know
  • void

You can also use a known interface or enum to type a variable, for instance:

enum Pokemon {
    'Bulbasaur', 'Charmander', 'Squirtle' 
}

interface Person {
    name: string;
    age: number;
    isCool: boolean;
    favoritePokemon: Pokemon;
}

var romain: Person = {
    name: 'Romain',
    age: 29, // but 30 soon !
    isCool: true, // here, writing 'no' or anything not boolean will emit a compile-time warning
    favouritePokemon: 'Charmander' // my favorite Pokemon is actually Joltean, but it's not the enum i defined... 
    // girlfriend: 'Your Mom' -> this will be rejected by Typescript since it's not in interface definition
};

When working with functions, you need to declare what types are its inputs and output:

function myFunction: string (a: number, b: number, c?: string) { // here c is optional
    return 'The result is ' + (a + b) + ' ' + c  ; // needs to be a string as i declared it
}

Arrow functions [ES6]

With ES6, anonymous functions (like callbacks) are written in a more compact way.

Instead of

function(){
    // some stuff
}

function(myVar){
    // some other stuff
}

function(var1, var2){
    // some different stuff
}

you can write

() => {
    // some stuff
}

myVar => {
    // some other stuff
}

(var1, var2) => {
    // some different stuff
}

Ultimately, this can lead to stuff like

mary => gina => robert => {
    // do stuff with mary, gina and robert
};

Be careful ! If you don't use braces, the output of your arrow function will have the type of whatever is returned. If you write something like

function say(something: string): string {
    console.log('I need to tell you', something);
    return 'Bye';
}

() => say('that your fly is undone'); // -> the output type of the arrow function is 'string'
() => {say('that your fly is undone')}; // -> the output type of the arrow function is 'void'

Import/export modules [ES6]

Importing and exporting modules are a bit different from what we are used to with npm.

Before you would have written

// In secretIngredient.ts
module.exports = 'Peanut Butter';

// In letsTakeCocaColaDown.ts
var secret = require('./secretIngredient');

console.log('The secret ingredient in Coca-Cola recipe is', secret); // Yay !!! 

With ES6, you write

// In secretIngredient.ts
export var secret = 'Peanut Butter';

// In letsTakeCocaColaDown.ts
import { secret } from './secretIngredient'; // This is much more readable

console.log('The secret ingredient in Coca-Cola recipe is', secret); // Yay !!! 

You can import/export several stuff very easily:

// In davidFavoriteSingers.ts
export var avril: Singer = 'Avril Lavigne';
export var taylor: Singer = 'Taylor Swift';
export var bob: Singer = 'Bob Dylan';

// In blackVelvetQuizz.ts
import { avril, taylor } from './davidFavoriteSingers'; // You can select the exports you need, because Bob Dylan is not really what David listens to...

You can choose to have a default export, this will allow you to get the most important stuff from your module easily, the rest not being exported unless you specifically ask for it with the previous syntax.

// In davidFavoriteSingers.ts
export var avril: Singer = 'Avril Lavigne';
export default var taylor: Singer = 'Taylor Swift';

// In blackVelvetQuizz.ts
import taylor from './davidFavoriteSingers'; // because we all know that David only has one favorite singer :p

You can change the imports names if you need to.

import { david as adel, romain as filip, christophe as franck, canelle } from './coloc2Be3';

If you need all the exports, you can use *. This is also useful if there is no default export.

import * as ants from './ants'; // you can use ants.romain, ants.alex, ants.armand, ants.david, ants.serge, ants.roxane ...

More details (but way less fun) on this page.

External modules

The problem

This section is important. We often work with a lot of external modules, because we don't want to be reinventing the wheelchair again. When doing so (npm install whatever --save), you will get your package. But TS won't know anything about it. That's a shame, because it will keep on saying

error TS2307: Cannot find module 'whatever'. // whateeeeeeeever ...

This is ok when you only have a couple of modules, but when you start having thousands of them, it can get messy to sort out what errors are really important to your logic.

Then you need to get TS acquainted with your new module.

Introducing tsd !

npm install tsd -g
tsd init
tsd install

With tsd, you can find and install TS definitions that people have written for their modules. And TS will then leave you alone ! You can find definitions using tsd query whatever or by going on this search engine.

tsd query whatever
- whatever / whatever // this means that 'whatever' exists in the tsd db !

tsd install whatever -s // this install the whatever.d.ts file in the 'typings' folder, reference it in tsd.json and make tsd.d.ts require it

The tsd.json is a configuration file similar to package.json, and the typings folder holds all .d.ts files, which are the actual TS definitions. Your project also gets a tsd.d.ts file which references all subsequent .d.ts files. tsd.json and typings/tsd.d.ts are created running tsd init.

How to use TS definitions ?

Add this line at the beginning to all your root .ts files : (yes, it's triple / ...)

/// <reference path="my/path/to/typings/tsd.d.ts" />

React

Here's an example of how you can use TS with React. What's important is to define the interfaces of your props and state, and reference them in the newly created component:

export default class MyComponent extends React.Component<myComonentProps, myComponentState>

even if you won't be using one of them.

Yeah, i know, i'm not using jsx. Deal with it !

import * as React from 'react';

import OtherComponentFromEarlier from './OtherComponentFromEarlier'; // this is a component you defined earlier using the same method

export interface myComponentData { // the pure data part of your component Props
    position: number;
    name: string;
}

export interface myComponentProps extends myComponentData{ // complete Props have an onClick method, which is not pure data, so you need to extend the myComponentData interface
    onSomethingClick: (id: number, isAvailable: boolean) => void;
}

interface myComponentState{} // here you don't have a component State, so no need to detail or export it


export default class MyComponent extends React.Component<myComonentProps, myComponentState> {

    render() {

        return React.createElement(
            'div', {
                // some stuff you want in the HTML element
                className: 'myClass',
                onClick: () => {console.log('I love Stacraft 2')}
            },
            React.createElement(OtherComponentFromEarlier, {name: this.props.name}),
            React.createElement('div', {}, this.props.position)
        )
    }
};

"Shut up and let me work"

You need to know that, even if TS shouldn't block you from working (it is supposed to generate the .js whatever happens), you might find necessary to tell him he's annoying.

  • Sometimes a module doesn't have a TS definition known by tsd. What you need to do is add something like this in your tsd.d.ts file:
declare module 'some-unknown-module' {export default {}}
  • Sometimes a function from an external module doesn't have a TS definition with expected signature.
declare module 'some-unknown-module' {
    export weirdFunction (arg1: any, arg2: any, arg3: any): Function;
}
  • Sometimes you really don't care about the type of the variable you want to use => any
function someRandomFunctionFromSomebodyElse (args) {
    return stuff;
}

This will issue something like

error TS7006: Parameter 'args' implicitly has an 'any' type.

This is a problem you should really solve, but sometimes it's not your code and you don't want to waste time on it. You can bypass this message either by not using the noImplicitAny option, or by writing

function someRandomFunctionFromSomebodyElse (args: any): any {
    return stuff;
}

However, it is not recommended to do so, it is just to solve issues that are not directly related to your own code.

@charsme
Copy link

charsme commented Feb 22, 2018

Amazing example.

There is a little typo on Types example, should be favoritePokemon. extra u on the favorite part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment