D3 v4 and TypeScript: getting set up

Introduction

A while ago, I wrote a couple of articles about creating charts with D3 and TypeScript. However, as time is flying by, those articles are now out of date, as both TypeScript and D3 got major updates since then.

This first article will just be about setting everything up to be able to use D3 v4 and TypeScript together, and creating a project basis that you can reuse, the same kind as what you can find here.

There will therefore not be much code in here, but quite some command line instructions. I will suppose that you have at least the following things properly configured:

  • a command line (DOS, PowerShell, bash, etc.) – I use Windows and PowerShell
  • a code editor – I use Visual Studio Code
  • a modern web browser – I use Google Chrome
  • npm

I will also assume you know how to use npm to install packages.

What we will do

As mentioned in the introduction, we will just set some things up in this part. With more details, we will:

  • install all the packages we need
  • configure the TypeScript compiler options
  • install and configure a module bundler (rollup)
  • install a minimal server for debugging purpose
  • watch our files for changes and have live reload as we are coding

Let’s get started!

TypeScript set up

Installing the packages

To be able to use TypeScript and D3, we will need to install:

  • TypeScript
  • D3
  • the D3 type definitions

The first thing to do is to create a folder and jump into it (name it what you like):

mkdir d3v4-with-ts
cd d3v4-with-ts

In that folder, initialize your package.json (the -y will reply yes to all basic questions):

npm init -y

Next, the packages installation:

npm i -S d3
npm i -D typescript @types/d3

One of the great things about the new version of TypeScript and the global movement towards it (thanks to the fact that the Angular team decided to embrace it) is that types are now available through npm. No need to install tsd or typings anymore, just plain npm packages.

Configuring the compiler

Now that everything is installed, let’s tell the TypeScript compiler how to build our files. At the root, create a file called tsconfig.json. Open it and configure it like this:

{
    "compilerOptions": {
        "noImplicitAny": true,
        "target": "es5",
        "sourceMap": true,
        "declaration": true,
        "module": "es2015",
        "moduleResolution": "node"
    },
    "exclude": [
        "node_modules",
        "dist"
    ]
}

This all tells the compiler that we want to:

  • disallow the use of implicit any. Everything should be typed. If not, we will need to be explicit about it
  • compile down to es5 version of JavaScript. Though most modern browsers support es6 syntax, using es5 is still a safer choice at the time of writing
  • generate source maps for files, so that we can debug the original files in the browser
  • generate declaration files (optional). This is useful for redistributing our work
  • use es2015 (or es6) module type. As it is part of the new JavaScript standard, this is the prefered way to create and consume modules as of now
  • resolve modules the same way node does. More details here. I am not knowledgeable enough to give more details, but I know this works

Also, we tell the compiler not to compile what is found under the node_modules and dist folders.

Does this work?

Now would be a good time to test this setup.
Let’s first create a folder to contain our source files (call it src). In the src folder, create an app.ts file, and paste in the following code (we’ll see what it does later):

import * as d3 from 'd3';

d3.select('body');

Jump to your command line and issue the following command:

.\node_modules\.bin\tsc

If everything wen well, you should not get any error, and your folder structure should look something like:
build01

But that command is a bit long, ain’t it? So let’s create an npm script to build our TypeScript files. In package.json, under the script section, enter the following line:

  "build": "tsc"

Now, just issuing

npm run build

should provide the same result. So far so good? Let’s continue!

Bundling things up

Installing packages

Our built code is currently in a state that cannot be used by the browser. Indeed, while the ES2015 specification defines how to write modules, it doesn’t specify how browser should load them. We therefore need to transform our modular code in code that can be interpreted by the browser. To do that, let’s use rollup:

npm i -D rollup

As we are building using node module resolution, we also need a plugin for that:

npm i -D rollup-plugin-node-resolve

Configuring rollup

Now that rollup is installed, we need to configure it to work. To do that, create a file called rollup.config.js at the root of the project, and write the following code in it:

import resolve from 'rollup-plugin-node-resolve';

export default {
    entry: 'src/app.js',
    dest: 'src/bundle.js',
    format: 'umd',
    plugins: [
        resolve({
            jsnext: true,
            main: true,
            module: true
        })
    ],
    moduleName: 'app'
};

This configures rollup to:

  • search for a file called src/app.js as its entry point (the one we created before)
  • output a file called src/bundle.js. This is the file we will reference in our index.html (I know, output a bundle in src is weird, but hey, this is development mode right?)
  • build our files in umd format for the most global use. We could also use iife here, if we do not intend to distribute our bundle
  • use the plugin we installed to resolve modules, using various options to retrieve modules from the node folder
  • create a variable app that will contain our code to avoid polluting the global scope (sort of namespacing)

Does this work?

To check if our setup is correct, let’s issue the following command:

.\node_modules\.bin\rollup -c

to bundle our compiled files. The -c flag tells rollup to use our configuration file. If you do not get error, you should now have a 6000+ lines file called src\bundle.js.

Again, command is too long to type every time. Let’s modify our build script to bundle our files:

    "build": "tsc && rollup -c",

Still ok? Moving on then!

Serving and watching

With everything set up, it would be great to actually see something show up. Also, it would be cool to just save our files and see the browser automatically refresh. Let’s get there!

Serving files

To serve our files and watch them as we code, we will install live server:

npm i -D live-server

We will then create an npm script to serve our files as we will need some command line arguments. In the scripts section of package.json, let’s add the following serve script:

   "serve": "live-server src --watch=src/**/*.html,src/bundle.js,src/**/*.css"

This will start a small server rooting at the src folder, and watch for changes on any html file, bundle.js, and any css file. Let’s now create an index.html file in the src folder, referencing our bundle file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>D3 with TypeScript</title>
</head>
<body>
    <script src="/bundle.js"></script>
</body>
</html>

(remove the spaces around the script markup).

Issuing the command

npm run serve

will open a web browser and serve your index.html. If you change your app.ts file to append something like

console.log('hello, world');

then issue npm run build in a command line, you should see the log trace in your browser console.

Watching files for changes

However, having to reissue the build command after every file change will quickly become annoying. Fortunately, there is a better way. First let’s install rollup-watch:

npm i -D rollup-watch

Then, let’s create two npm scripts that will watch our TypeScript files, build them on change, watch our js built files and bundle them on change:

    "tsc:w": "tsc -w",
    "rollup:w": "rollup -c -w"

The first one watches and builds the TypeScript files, the second one bundles them (notice on both lines the -w flag).
Open two more command lines, and run both script, one in each:

npm run tsc:w
npm run rollup:w

If you now change your app.ts file to change the log trace to

    console.log('hello, watch mode');

you should see the new log trace in the console after saving.

So, 3 scripts to start every time?

No, of course not! To synchronize this all, we will install yet another npm package (the last one), npm run all:

npm i -D npm-run-all

npm run all allows to run multiple npm scripts in a single command.

To use it, we will create a start script in our package.json:

    "start": "npm-run-all --parallel serve tsc:w rollup:w"

start is a special script in npm, and does not need the run flag to be used. Stop all your watchers (using ctrl+c), and issue the single following command:

npm start

And all your watchers are now enabled. Finally, rename the build script to prestart, so that it is executed prior to the start script, and files get watched properly.

Is that it?

Yes, yes it is! Thanks for bearing with me until here. You now have a clean ground to start writing your D3 code with TypeScript.

If you want to go on and create and actual chart, visit this article.

The files for this completed setup can be found here. If you have any question or comment, do not hesitate to get in touch, either here or via twitter.

If you want to share or reuse this basic project, my current strategy is via a GitHub repository, that I clone every time I want to create a new project. Think about adding the following lines to your .gitignore file to avoid checking in your built files:

src/**/*.js
src/**/*.js.map
src/**/*.d.ts
Leave a comment

11 Comments

  1. Scott

     /  December 1, 2017

    Hey Hugues, thanks for the article! I noticed a lot of the of the code snippets appear as HTML escaped… for example a double quote appears as " instead of literally. (Hopefully my comment renders that appropriately.)

    Like

    Reply
  2. Scott

     /  December 1, 2017

    Yep; looks great. Thanks again–I’d been meaning to get going with TypeScript and D3 for *years* and this (and the preceding article) were the perfect way to start.

    Like

    Reply
  3. Marti Nito

     /  March 9, 2018

    Hey there, the rollup config format changed. I succeeded with

    import resolve from ‘rollup-plugin-node-resolve’;

    export default {
    input: ‘src/app.js’,
    output: {
    format: ‘umd’,
    name: ‘app’,
    file: ‘src/bundle.js’
    },
    plugins: [
    resolve({
    jsnext: true,
    main: true,
    module: true
    })
    ],
    };

    Like

    Reply
  4. Peter Laman

     /  April 12, 2022

    Hi Hugues, it’s 2022 now and doesn’t work anymore – at least when I try it. The first ts compilation fails with undefined types in D3 (261 errors in total).
    After tweaking the tsconfig.json a bit:
    “module”: “ES6”,
    “target”: “ES2020”,
    it compiles.

    Then, the rollup fails: “[!] Error: You must supply options.input to rollup”

    After adding options.input, it reports 15 circular dependencies in d3… That’s where I get stuck…

    Like

    Reply
    • Hi Peter, thanks for your interest. I did not use rollup in a while, and am not surprised that the code does not work fully anymore. There is a comment above (from 2018…), that might help you. Depending on your use case, you migh also consider using webpack as a bundler, as it is more commonly used nowadays. But I am afraid that’s as much help as I can provide

      Like

      Reply
  1. Creating a chart with D3 (v4) and TypeScript (or ES6) | Wandering in the community

Leave a comment