Skip to content

TypeScript

TypeScript is a superset of JavaScript which primarily provides optional static typing, classes and interfaces. One of the big benefits is to enable IDEs to provide a richer environment for spotting common errors as you type the code.

Instead of only adding types to standard JavaScript like flow does TypeScript is a language of its own. It is aimed for large projects to get more robust software, while still being deployable as regular JavaScript.

I for myself find TypeScript better supported, better community and faster developing so after my first steps with flow+babel I switched to TypeScript.

Installation

First you need to install TypeScript:

npm install --save-dev typescript ts-node @types/node eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

To make it usable you also have to add a configuration file tsconfig.json in your project root:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "dist",
    "declaration": true,
    "sourceMap": true,
    "removeComments": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "files": [
    "./node_modules/@types/mocha/index.d.ts",
    "./node_modules/@types/node/index.d.ts"
  ],
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

The concrete configuration depend on the target environment.

tsconfig.json:

{
  "compilerOptions": {
    "lib": ["es2017"],
    "module": "commonjs",
    "target": "es2017"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "lib": ["es2018"],
    "module": "commonjs",
    "target": "es2018"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "lib": ["es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"],
    "module": "commonjs",
    "target": "es2019"
  }
}

package.json:

{
  "type": "commonjs"
}

tsconfig.json:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "lib": ["ES2020"],
    "module": "ES2020",
    "moduleResolution": "node",
    "target": "ES2020"
  }
}

package.json:

{
  "type": "module"
}

tsconfig.json:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "lib": ["ES2021"],
    "module": "ES2020",
    "moduleResolution": "node",
    "target": "ES2021"
  }
}

package.json

{
  "type": "module"
}

Usage

Now you can integrate it in your package.json:

{
  "scripts": {
    "build": "rm -rf lib && tsc",
    "prepare": "npm run build",
    "test": "find test/mocha -name '*.ts' | xargs mocha -r ts-node/register --exit && find test/mocha -name '*.ts' | DEBUG=async* xargs mocha -r ts-node/register --exit",
    "coverage": "nyc -r lcov  --reporter=text -e .ts -x \"test/*\" npm run test; xdg-open coverage/lcov-report/index.html",
    "preversion": "npm test",
    "postversion": "npm publish",
    "postpublish": "npm run stats && git add -A && git commit -m \"Update documentation\"; git push origin --all && git push origin --tags",
    "start": "node dist/index"
  },
  "engines": {
    "node": ">=12"
  }
}

Also specify the engines information to help people use your module in the right version.

This defines the transformation to:

  1. Be used as JIT compiler in dev mode (run using npm run dev)
  2. Be converted into ES6 code by running npm run build (into dist folder)
  3. Everything in dist folder can be used without TypeScript
  4. Before publishing to npm the build command is called automatically

Test

In testing typescript private members and functions are not accessible. You will get a compile error. Netherless it will be accessible in the generated JavaScript at the moment.

But for testing it is often needed to access them. This should be done by changing the source code without really making the generated code complexer. To do this I implemented it in the following solution.

Use the access modifiers as follows:

  • public - completely accessible
  • protected - things which should be available in extended classes or test
  • private - something you only need in the class

As you see I use the protected for something needed in test like:

class Work {
  protected load: integer
  constructor() {
    this.load = 1
  }
}
exports default Work

Now to access the load property I use a wrapper class within the test folder (so it will not go into the dist):

import * as Work from "../../src/Work"
class TWork extends Work {
  public getLoad(): integer {
    return this.load
  }
}
exports default TWork

Now within the test I use the added getLoad() method:

import * TWork from "../wrapper/TWork"
const work = new TWork()
expect(work.getLoad()).to.equal(1)

The directory structure used looks like:

src/Work.ts
test/
  mocha/test.ts
  wrapper/TWork.ts
dist/Work.js

Last update: May 9, 2021