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.
npm install -g typescript
This will give you access to tsc
Typescript compiler.
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 asany
--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 isES3
, but we useES5
--module commonjs
: specifies that you can to use modules. We usecommonjs
, 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.
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.
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 yourbrowserify-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.
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.
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/knowvoid
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
}
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'
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.
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
.
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" />
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)
)
}
};
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 yourtsd.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.
Amazing example.
There is a little typo on Types example, should be favoritePokemon. extra u on the favorite part.