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 supportes6
syntax, usinges5
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:
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 useiife
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
Scott
/ December 1, 2017Hey 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.)
LikeLike
Scott
/ December 1, 2017^^^ My comment also did not render as intended, and I don’t know how to update or preview … Hopefully you know what I meant.
LikeLike
huguesstefanski
/ December 1, 2017Thanks for pointing this out… I will try and fix this this week-end if I find time!
LikeLike
huguesstefanski
/ December 1, 2017Should be ok now
LikeLike
Scott
/ December 1, 2017Yep; 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.
LikeLike
huguesstefanski
/ December 4, 2017Thanks, happy to help 🙂
LikeLike
Marti Nito
/ March 9, 2018Hey 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
})
],
};
LikeLike
huguesstefanski
/ March 13, 2018Thanks for the heads up. I will try and update the article and code once I find time…
LikeLike
Peter Laman
/ April 12, 2022Hi 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…
LikeLike
huguesstefanski
/ April 19, 2022Hi 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
LikeLike