YouTube Video

Introduction.

In NodeJS you can create a command line tool which will run on the NodeJS platform. Most of the developer tools like Grunt, Gulp, even npm it self run like this. But NodeJS only run JavaScript which is nice but if you want to use TypeScript you have to compile the TypeScript code to JavaScript first

So in this post I’m going to explain how to use TypeScript as development language and how to compile the TypeScript to JavaScript so it can be run on the NodeJS platform.

We will be creating a tool to calculate factorial of a number given as an argument to the tool.

Prerequisite.

Since everything from development tools to the end product will be running on NodeJS install NodeJS.

  1. Download and install for Node.js
  2. Using NVM

    or

  3. Using OS default package manager (search for node or nodejs)

Also you need a good editor, you can use the operating system’s default text editor but for TypeScript Visual Studio Code is a better choice since it has native support for TypeScript so it make your development easier.

TypeScript Project

Folder structure.

The Initial folder structure will be as below.

ts-factorial-cli
├── package.json
└── src

The root folder name can be anything its up to you.

src folder is where out typescript code resides. package.json is for NodeJS metadata file, to create it use the command npm init which will ask some questions answer those and the file will be created which will somewhat looks like the following.

{
  "name": "ts-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Karthikeyan Vaithilingam",
  "license": "ISC"
}

TypeScript Node Module

To add TypeScript to the project we will be installing typescript node package. To do that use the following command.

Karthik's - Terminal
$ npm install typescript --save-dev
    

TypeScript install as a development dependency thats what --save-dev flag denotes. If you need a Node module to be used in compiled JavaScript code use --save flag.

The above command will add a entry in the package.json's devDependencies section

{
  "name": "ts-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Karthikeyan Vaithilingam",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^2.6.1"
  }
}

TypeScript Config file.

The configuration for the TypeScript compiler will be maintain in tsconfig.json, create a file with the same name and have the following content.

{
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ],
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6"
    }
}
  • include is a list of patterns for the source to be compiled.
  • exclude is a list of patterns for the TypeScript compiler not to include.
  • compilerOptions
    • module which JavaScript module loader to be used in compiled JavaScript.
    • target target version of JavaScript.

Note: NodeJS dose not support es2015’s modules at least not yet at the time of writing this post.

TypeScript compile npm script.

To compile TypeScript we will be using npm scripts to do so.

In package.json add the following section.

"scripts": {
    "build": "tsc --outDir bin"
}

To run the above script use npm run build this will create a folder called bin and the compiled javascript will be placed there.

Now create a file in src folder named main.ts and have console.log('Test') since TypeScript is a super set os JavaScript all valid JavaScript statements are valid TypeScript statements.

Then do npm run build which will create the compiled JavaScript file bin/main.js the content will be identical to main.ts because there is nothing to compile since the statement is JavaScript.

Run the JavaScript with node ./bin/main.js you get Hello World as output.

Now let us test TypeScript class. Change the contest of main.ts to the following

export class Main{
    static log(val:any){
        console.log(val);
    }
}
Main.log('Hello World');

The compiled ES6 JavaScript code will be.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Main {
    static log(val) {
        console.log(val);
    }
}
exports.Main = Main;
Main.log('Hello World');

Now lets try with ES5 to do so change the tsconfig.json compiler options to "target": "es5" and do npm run build. The result will be.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Main = /** @class */ (function () {
    function Main() {
    }
    Main.log = function (val) {
        console.log(val);
    };
    return Main;
}());
exports.Main = Main;
Main.log('Hello World');

Diff between ES6 and ES5

Diff between ES6 and ES5

Factorial In TypeScript

In the above created project we will add factorial class to do the factorial.

Create a file named factorial.ts in src folder with the following content.

export class Factorial {
    private num: number;

    constructor(num: number) {
        this.num = num;
    }
    getFactorial(): number {
        return this.doFact(this.num);
    }

    private doFact(num: number ): number {
        return num <= 1 ? 1 : num * this.doFact(num - 1);
    }
}

You can have the factorial function in the same file. I’m having it in a separate file to demonstrate the class and import functionally of TypeScript.

Now in main.ts replace the existing content to the following.

import { Factorial } from './factorial';

const argc = process.argv.splice(2);

if (argc.length != 1) {
    console.error(`Invalid number of arguments ${argc}`);
    process.exit(1);
}

const inputNumber = argc[0];

if (! /^\d+$/.test(inputNumber)) {
    console.error(`Invalid input ${inputNumber}`);
    process.exit(1);
}

const fact = new Factorial(+inputNumber);
console.log(`Factorial of ${inputNumber} is ${fact.getFactorial()}`);

If you build now using npm run build you will get compilation errors as follows.

Karthik's - Terminal
$ npm run build

> [email protected] build /Volumes/Tech/Dev/Projects/Else/ThrowAway/Typescript/ts-factorial-cli
> tsc --outDir bin

src/main.ts(3,14): error TS2304: Cannot find name 'process'.
src/main.ts(7,5): error TS2304: Cannot find name 'process'.
src/main.ts(14,5): error TS2304: Cannot find name 'process'.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build: `tsc --outDir bin`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/karthik/.npm/_logs/2017-11-22T12_57_18_029Z-debug.log
    

This because TypeScript compiler doesn’t know what is process is, to solve this we have to install node types using npm install @types/node --save-dev which will add type definition for NodeJS APIs. Now your build will be successful.

The main file will accept the input number as argument and print the factorial of it. Test it with the following.

Karthik's - Terminal
$ node ./bin/main.js 5
Factorial of 5 is 120
    

Command Line Tool

To make the script to a command line tool there two steps to be done so it can be executed with the node command.

1. She Bang.

In *nix environment to execute a file directly she bang is used for mentioning the executor.

So add the following in the src\main.ts at the first line

#!/usr/bin/env node

2. NPM Bin

To inform npm that there is a command line executable available in the node module we have to add the following section.

"bin": {
    "factorial": "./bin/main.js"
}

The key of the property will be the command name and the value will be the compiled JavaScript file for that command.

Testing the Tool

To test the tool there are two ways.

npm link will create a symbolic link to you project folder in the global node_modules so you project will behave a it is an install global node module. This is very much useful for development since all the changes are effective immediately.

npm install -g

Apart form npm link you can also do npm install -g which will copy the the project in the global node modules folder.

Testing the command

To test simply use factorial <number>.

Karthik's - Terminal
$ factorial 5
Factorial of 5 is 120
    

Reference.

A sample factorial tool source is available Github. This repo has some additional features also like linting.