Typescript Study Notes

How to use Typescript and the current JS toolchain with minimal overhead

For someone like me who has learned JavaScript when it was still only used in the browser, the new node-world can be intimidating at times. It feels like there are a lot of tools and configurations that need to be used while half of them are probably totally unnecessary for my needs. Yet many developments like Typescript or proper namespacing are very positive. So, this is my attempt to catch up with 2018 while avoiding the bloat.

TL; DR

You can clone a basic Typescript project that includes D3 as a library on GitHub. I use this template myself whenever I start a new visualization project. It uses:

Modules

Modules are the new scoping mechanism in JavaScript. They basically replace the old style of self-invoking anonymous functions of:

var jQuery = (function() {
    ...
})();

The important thing about modules is, that they have their own scope. The module needs to explicitly export variables so that they are accessible via import in other modules.
The syntax for this has been standardized in ES2015 and it's the same in Typescript.

// regexes.js
export const numberRegexp = /^[0-9]+$/;
// script.js
import numberRegex as numPattern from 'regexes';

A pretty exhaustive and readable explanation can be found in the Typescript Documentation. Previously, there have been many other approaches. But Typescript will hide typically translate into the right syntax, depending on your Target.

Dependency management

npm is included with Node.js and is pretty much the standard nowadays. It's solid and provides everything one would usually want. npm's package.json not only contains dependencies, but conveniently also some build scripts.

{
    "name": "ProjectName",
    "dependencies": { 
        "d3": "^5.5.0", 
    },
    "devDependencies": {
        "typescript": "^3.0.1",
        "webpack": "^4.16.5", 
        "@types/d3": "^5.0.0",
        "webpack-cli": "^3.1.0",
        "webpack-dev-server": "^3.1.5" 
        "ts-loader": "^4.4.2", 
    },
    "scripts": {
        "start": "webpack-dev-server --open", 
        "build": "webpack && cp -r static/* dist/" 
    }
}

Typescript

Typescript brings many very welcome additions to JavaScript. Because it is typed, it can provide the developer with a faster feedback cycle. It will for example immediately throw an error when I try to pass a string where an array of numbers is expected.

Typically, I would only have found out when running the code in the browser and maybe not even then. If, for example, the function was only checking the length of the argument.

Typescript also makes autocomplete more useful for JavaScript. It even provides a language server and thus works with my beloved VIM.

Adding types to code you write yourself is relatively straightforward.

data:number[] = [1,2,3]
tsc -m 'es2015' index.ts

Options for compilation can be saved in a tsconfig.json-file:

{
  "compilerOptions": {
    "sourceMap": true,
    "allowJs": true,
    "lib": ["dom", "es5"]
  }
}

There we have a minimal useful set. Sourcemaps and allowing the import of JavaScript libraries (see Type definition files). The lib-option defines, which parts of the standard-library to include. When developing for the web, we want to be able to use everything that is DOM-related for example. All the options can be found in the documentation.

Type defintion files

Because Typescript is a superset of JavaScript, we can still use all the existing JavaScript libraries without a problem. Typescript will import libraries from the node_modules folder just fine.

The only problem with this is, that there they don't have any type definitions. So, all the benefits types don't apply for most of the third-party code.

Luckily, there is a workaround. For many popular libraries, people have created separate type definition files. These define the interface of a library. It's somewhat like a header-file in C:

// @types/d3-selection/index.d.ts
…
attr(name: string, value: null): this;
…

The convention is, that the type definitions can be found on npm under the same name as the library but with a @types/-prefix. For example, @types/d3.

These files are regular npm packages that get installed into the @types-directory and contain index.d.ts-files with just type definitions.

Webpack

The standardized import syntax is already supported by some major browsers. But it's still a bit early to rely on it alone.

Additionally, we would like to use npm to manage all the external libraries. But browsers don't go and look into the node_modules directory to find dependencies.

For these two reasons, we use Webpack to combine our code and all the dependencies into a single main.js file which can then be included the old-school way:

<script src="main.js"></script> 

Webpack configuration is a bit trickier:

const path = require('path'); // Node specific stuff

module.exports = {
  entry: './src/index.ts',           // 1
  devtool: 'inline-source-map',
  mode: "development",
  resolve: {                        // 2
    extensions: [ '.ts', '.js' ] 
  },
  module: {
    rules: [                        // 3
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  output: {                         // 4
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
};

We tell Webpack to start at ./src/index.ts 1 . Webpack will automatically look for our imports in src and its subdirectories as well as in node_modules. Because we don't define the file extension when importing in Typescript, there is the extensions-part in 2. In 3 we tell Webpack to use the ts-loader-library when it encounters a .ts-file (so it gets transpiled into JavaScript). Finally, output 4 defines where the combined files should be written to.

We end up with the following directory structure:

Directory structure

And finally, an empty project is ready and the first code may be written. The shortcut is to just clone it from GitHub.