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:
- npm for package management
- Typescript to code in
- Webpack to combine all the files into one that can be included with
<script>
-tags.
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 it is also a place to define som esimple 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 you try to pass a string where an array of numbers is expected.
Typically, one 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.
Finally, Typescript code needs to be converted to JavaScript in order to be run:
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, you 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, you 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 aren't any type definitions in JavaScript. 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 import
s 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:
And finally, an empty project is ready and the first code may be written. The shortcut is to just clone it from GitHub.