Working with TypeScript Projects

Published on: 06, by

About a month ago, I was placed in a fairly large project with 40+ microservices, 50+ git repos, managed by three teams of 8 engineers average.

The project was mainly based on plain JavaScript with minimal documentation. And because JavaScript is a dynamically-typed, it very challenging to read and use any of the custom libraries.

Oh how I long for a strongly type language!

To be fair, the team had a good unit tests which gave me hint on the signature of the functions.

On my spare time, I started researching about TypeScript, the tooling ecosystem. It turns out, most of the JS tool/library, nowadays there is a counterpart (or type-definition) in TypeScript.

Below are the tools and knowledge that I found useful working with TypeScript project.

[TOC]

[[TOC]]

0. Installing TypeScript

$ npm  install -g typescript

Then within a node project, you can inintialize as ts project by generating the tsconfig.json file.

$ tsc --init

​1.​ Helper Tools

Tools can help with the efficient development of a TypeScript project. Here I will cover two categories: testing and guardrail.

​1.1.​ Testing

There are many testing frameworks including :

I found Jest to be feature rich with a healthy community, so I will focus on it.

Here is my suggestions with super quick usage guide: The guide assumes you already have node installed.

  1. Use Jest, OOTB it includes coverage (istanbul), spies, and other useful features

    • $ yarn add --dev jest @types/jest ts-jest Once installed you can add in package.json the script "test": "jest"
  2. Configure coverage in Jest. (Jest includes Istanbul) In jest.config.js

    module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'node',
    setupFiles: ["dotenv/config"],
    collectCoverageFrom: [
    "**/*.{js,ts}",
    "!**/node_modules/**",
    "!**/coverage/**",
    "!*.config.js",
    "!**/env.ts",
    ],
    coverageReporters: [
    "json",
    "lcov",
    "text"
    ],
    transform: {
    ".ts": "ts-jest"
    },
    coverageThreshold: {
    "global": {
    "branches": 2,
    "functions": 4,
    "lines": 10,
    "statements": 10
    }
    },
    };

    To run the test with coverage $ jest --coverage or $ yarn test --coverage

  3. For mocking HTTP requests, use nock, the http server mocking and expectation library. → It will facilitate testing your libraries that depends on external services.

    • $ yarn add --dev nock @types/nock
    • An alternative to nock is msw
  4. For automated browser testing, use Playwright. It uses Chrome protocol, supporting event model → No need to rely on flaky sleep()’s, yielding a more reliable tests.

    • Alternative includes
      1. Cypress → Fast. Runs on browser, chrome only.
      2. TestCafe → Non Selenium-based. Supports Chrome and Firefox.
      3. puppeteer → Playwright forked off from this project.
      4. Nightwatch → Selenium-based with JS-binding.
      5. Any other Selenium-based.

​1.2.​ Guardrails

Guardrails prevents you from making mistakes.

  1. static code analysis, use typescript-eslint. A tool based on ESLint (TSLint has been deprecated in favor of typescript-eslint)

    • $ yarn add --dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

    • Add .eslintrc.yml file with

      extends:
      - eslint:recommended
      - plugin:@typescript-eslint/recommended
      - prettier/@typescript-eslint
      - plugin:prettier/recommended
      parser: "@typescript-eslint/parser"
      parserOptions:
      ecmaVersion: 2017
      sourceType: module
      plugins:
      - "@typescript-eslint/eslint-plugin"
      rules:
      curly: error
      "@typescript-eslint/no-explicit-any": off
      "@typescript-eslint/no-var-requires": off
    • Also you can add files and folders to be ignored in .eslintignore

    • In package.json add script to execute with npm or yarn:

      "lint": "eslint --fix --ext .ts,.json ./src"
  2. For code formatting, use Prettier → Team, and y ourself, will adhere to formatting standards, improving readability and avoiding unnecessary diff lines on MRs.

    • $ yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier

    • Add .prettierrc.js file with

      module.exports = {
      semi: true,
      trailingComma: "all",
      singleQuote: true,
      printWidth: 120,
      tabWidth: 4
      };
    • In package.json add a script: "test": "jest"

  3. To add git hooks configuration that is checked in the repo, use husky → this will share the git hooks with the rest of the team members.

    • $ yarn add --dev husky

    • In package.json add

      "husky": {
      "hooks": {
      "pre-commit": "yarn lint",
      "pre-push": "git diff HEAD --quiet && yarn test"
      }
      }
  4. For faster lintin (linting only those that has chenged, uss lint-staged → this will lint only those files that are staged reducing the time it takes to run the lint

    • $ yarn add --dev lint-staged

In addition, test for vulnerabilities in 3rd party packages using Retirejs.

$ npm install -g retire
$ retire

​2.​ Publishing Custom TS Module with Type Definitions

If you want to reuse a custom TS library (module) in different other projects, you can publish it to a node package registry. Different from a plain ES library, a TS library also requires type definition files to be present for the consuming application to take advantage of the typing information.

This is the quick guide:

  1. Configure tsconfig.json file to enable declaration → this will generate corresponding .d.ts files

    • Under compilerOptions, add (or uncomment)"declaration": true
  2. In package.json add types entry.

    "main": "./dist/main.js",
    "types": "./dist/main.d.ts"
    • "typings" field is synonymous with "types"
  3. Set dist as distribution folder

    • You may need to modify the tsconfig.js file to have outDir set to dist.
    • You do not want dist to be checked, so add it in .gitignore.
    • Create .npmignore based on .gitignore, but without the dist folder → Without the .npmignore the publish action will take .gitignore for the files to ignore publishing.
  4. Modify the package.json to include publishing details

    ...
    "publishConfig": {
    "registry": "<URL-OF-YOUR-OWN-REGISTRY>"
    },
    "prepublish": "tsc"
    ...
    • The publishConfig.registry is needed if you want to publish in a different registry.

Once the above is done, you can publish the package to the registry by running

$ yarn publish

Or alternatively

$ npm publish

Yarn will ask for a new version, whereas npm will not.

The registry will probably fail with 401 if you use the same version to publish, as they are not meant to be overridden.

​2.1.​ Using scoped packages

It is possible to assign scope to packages, an example would be @material-ui/core. Scoping can eliminate conflict with other module names. Also allows associating different registry location.

If you are publishing a module, to use scope, the package name should start with the scope name, e.g.

# this is package.json
{
"name": "@my-group/my-amazing-module"
...
}

If you are using a module and want to associate a scope to a specific registry, you need to add the details package manager’s rc files

For NPM Add in the ~/.npmrc file (in Windows %USERPROFILE%/.npmrc):

@<SCOPE>:registry= https://MY-REGISTRY-URL

Or you can also do it by npm command:

npm config set @&lt;SCOPE>:registry

For Yarn The file to modify is .yarnrc

"@<SCOPE>:registry" "https://MY-REGISTRY-URL"

​2.2.​ References

  1. Typescript Publishing, typescriptlang.org
  2. The 30-second guide to publishing a TypeScript package to NPM, Medium

3. Debugging TypeScript

You wIll need to enable source map generation.

In tsconfig.json set either

"sourceMap": true,

or

"inlineSourceMap": true,

References

  1. TypeScript Debugging

Published on: 06, by

Edit on Git