DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 964,423 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Bernhard HΓ€ussermann
Bernhard HΓ€ussermann

Posted on • Updated on

Adding TypeScript-support to your Node.js project

Many Node.js projects will reach a level of complexity where we'll often find ourselves wondering what a certain object's structure looks like. We'll also have the need for early warnings about errors due to trying to access fields that don't exist or assuming different types from what they really are. These are good indications that you would derive a lot of benefit from using a type-checking system such as TypeScript. This article will look into how to make the transition from JavaScript to TypeScript in an existing project.

TypeScript-support for Node.js is provided by the typescript NPM package. The best way to implement this package will depend on your project's build tooling. If you have webpack set up, then the easiest way will be by using the ts-loader package (see the "Setting up ts-loader for webpack" section below for this ts-loader + typescript setup). However, if you don't have a module bundler configured, then the simplest way to add TypeScript will be through the tsc ("TypeScript compiler") command, which is included in the typescript package. This tsc setup is described next.

Setting up TypeScript compiler (tsc)

tsc is the official TypeScript transpiler which converts your TypeScript source files into JavaScript files that can be executed by Node.js or the browser. This section assumes you have a working Node.js project where you run your main js-file directly using node.

The first order of business is adding the typescript package to your project as a dev-dependency:

npm install --save-dev typescript
Enter fullscreen mode Exit fullscreen mode

The next step is to create a config file for typescript. A good starting point is to generate the file using the command npx tsc --init. Add the following properties to the compilerOptions property in the generated file:

  • "noImplicitAny": true β€” Disallows the use of the any type - a common anti-pattern in TypeScript.
  • "allowJs": true β€” Allows us to have JavaScript (.js) files amongst the TypeScript (.ts) files. When we need to migrate an existing JavaScript project to TypeScript, this allows us to systematically convert files from JavaScript to TypeScript one at a time. Once the conversion is complete, this flag can be removed.
  • "outDir": "dist" β€” The folder where the transpiled JavaScript files will be placed.
  • "rootDir": "src" β€” The location of your TypeScript / JavaScript source code.

After adding these properties the tsconfig.json looks as follows:

And just like that, the code is ready to be compiled! Just run the command npx tsc and see the output files appear in the dist folder.

Before trying to run the compiled code, keep in mind that tsc outputs CommonJS-style JavaScript. This means that if your source code is written as ES modules, you need to change the "type" property in your package.json from "module" to "commonjs" in order to run the compiled code (tsc will still interpret your source code as ES modules). At this point the "main" .js file in the dist folder should run successfully via the node command: node dist/my-app.js

Source maps

A side-effect of running the compiled code instead of running the source code directly is that stack traces of errors will refer to the line numbers inside the compiled code instead of in the source code, which is not very helpful. Luckily we can have tsc generate source map files which map each line in the compiled code to the corresponding line in the source code. These can be used to make our application report the correct line numbers in error stack traces.

Getting tsc to generate the source map files is an easy matter of adding the "sourceMap": true property to the "compilerOptions" in tsconfig.json:

Run npx tsc again and note that in the dist folder a .js.map file is created for each compiled .js file. However, we still need to make these mappings be interpreted at run-time. To do this, add the source-map-support package as a run-time dependency. We also add its types declaration package for TypeScript as a dev-dependency:

npm install --save source-map-support
npm install --save-dev @types/source-map-support
Enter fullscreen mode Exit fullscreen mode

And activate it by adding the following to your main source file:

Compile and run the application. Error stack traces will now refer to the lines in the source code.

Adapting script commands

Creating script commands (in package.json) for compiling and running the application is pretty simple:

"build": "tsc",
"run": "node dist/my-app.js",
Enter fullscreen mode Exit fullscreen mode

For a streamlined developer experience we would want to have a command that will listen for source file changes and then recompile and restart the application whenever they occur.

The tsc command conveniently has a --watch flag we can use to recompile. Then we can use the nodemon package to restart the application whenever we detect file changes in the dist folder (due to the recompilation). Hence we can have the following two scripts:

"build:watch": "tsc --watch",
"run:watch": "nodemon dist/my-app.js --watch dist",
Enter fullscreen mode Exit fullscreen mode

But we need these two commands to run at the same time. This can be achieved using the npm-run-all package.

Add the required packages as dev-dependencies:

npm install --save-dev nodemon npm-run-all
Enter fullscreen mode Exit fullscreen mode

Final list of scripts:

"build": "tsc",
"run": "node dist/my-app.js",
"build:watch": "tsc --watch",
"run:watch": "nodemon dist/my-app.js --watch dist",
"start": "npm-run-all --parallel build:watch run:watch"
Enter fullscreen mode Exit fullscreen mode

Run npm start to compile and run the application. Whenever you make a change to a source file, the application will automatically recompile and then restart.

Setting up ts-loader for webpack

If your application already has build tooling set up via webpack, the easiest way to add TypeScript support is by using the ts-loader package.

This time around, add the ts-loader and typescript packages as dev-dependencies:

npm install --save-dev ts-loader typescript
Enter fullscreen mode Exit fullscreen mode

The same tsconfig.json configuration file as above can be used in this case:

Note that in this case, since webpack is already configured to process JavaScript source files, there is no need for including the "allowJs": true flag here, unless you would like both JavaScript and TypeScript files to be processed by ts-loader. If this is the case, make sure to include the extension js in the "test" property of the rule added to webpack.config.js below.

In webpack.config.js add a rule telling webpack to invoke ts-loader for all TypeScript files:

At this point the application should build and run fine. We are now ready to start converting .js files to .ts files.

Migrating existing JavaScript files to TypeScript

At this point we should have a tooling setup capable of compiling a combination of JavaScript and TypeScript files. This means we can systematically convert JavaScript files to TypeScript one at a time, compiling and testing the application along the way by renaming a .js file to .ts and fixing the compiler errors as they come up. Once all .js files in the project have been converted, the "allowJs": true flag in the tsconfig.json can be removed.

Here are some general notes to observe during this conversion:

use strict

Any 'use strict' directives in existing JavaScript files can be removed since the "strict": true setting in the tsconfig.json causes 'use strict' to be generated into the compiled JavaScript files automatically.

Extending the Error class

If you have defined any sub-classes of Error, note that there is a known bug in TypeScript whereby testing for an instance of this error using instanceof will not work.

See this StackOverflow post for a work-around. If you have multiple sub-classes of Error, I would recommend applying the work-around to a common "base" error class (eg. class ErrorBase extends Error) and have all other error classes extend this class.

Alternatively, if your code need not support running on IE 11, you should be able to safely change the compiler target from ES5 to ES6 by changing the "target" property in tsconfig.json to "es6" (see the ES6 compatibility chart). This way tsc will generate all classes as actual ES-classes in the target code, effectively circumventing the bug and obviating the need for the work-around.

Conclusion

There are many benefits to TypeScript that make it worthwhile to take the time to set it up for new projects, and even to convert from JavaScript in existing projects. Making the necessary changes to an existing project's build tooling is generally quite simple, and for new projects there is no need to add a module bundler just to be able to use TypeScript, thanks to tsc.

I have applied this migration to a project from my other articles. Feel free to view the version with the tsc setup or the version with the webpack / ts-loader setup on GitHub.

Top comments (3)

Collapse
 
crazyoptimist profile image
crazyoptimist

Nice post πŸ‘

Collapse
 
joelbonetr profile image
JoelBonetR

It can be a lot easier than that, just depends on you working on OOP or not. check this out πŸ˜„

Collapse
 
rilusmahmud profile image
Rilus Mahmud

I was actually searching something like this!
Thanks ✌️

This post blew up on DEV in 2020:

js visualized

πŸš€βš™οΈ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! πŸ₯³

Happy coding!