The various type systems for JavaScript take different approaches, but all of them serve the goal of defining different data types for an application and referencing them in the source code.
Normally, the data types of the type systems are used in connection with variables and function signatures.
In addition to providing a set of rules and a JavaScript syntax essay, most type systems also provide programs you can use to check your application. This step occurs before the application is executed. Before we turn to TypeScript, the most widely used type system, let’s consider Flow as an alternative.
Flow
Flow is a type system developed by Facebook. It came to prominence through its use in the frontend of React applications. The tool is available as an open-source project. The project’s official website can be found at https://flow.org/en/. Flow’s source code is maintained on GitHub at https://github.com/facebook/flow. It’s installed via the package manager, that is, either Node Package Manager (npm) or Yarn. The developers recommend local installation as a development dependency. Before you launch the installation, you must make sure that your project has a package.json file. If you haven’t created one yet, you must first run the npm init -y command. Then you must always add the type and private fields to the generated file to be able to use the ECMAScript module system and to ensure that your application can’t be published accidentally.
13
{
"name": "node_book",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
In the next step, you install Flow via the npm install -D flow-bin command and initialize it using the npx flow init command. This command creates the .flowconfig file in your project, which contains the configuration.
Each source code file you want to check with Flow must start with this comment: /* @flow */. Below shows an example of a Flow source code.
/* @flow */
class Movie {
title: string;
year: number;
rating: number;
constructor(title: string, year: number) {
this.title = title;
this.year = year;
}
}
function rateMovie(movie: Movie, rating: number) {
movie.rating = rating;
}
const ironMan: Movie = new Movie('Iron Man', '2008');
rateMovie('Iron Man', 4);
If you save the source code in a file named index.js and then run the npx flow check command, Flow checks the source code of your application and issues the messages shown in the listing below for the sample code.
Error ----------------------------------------------- index.js:16:46
Cannot call Movie with '2008' bound to year because string [
1] is incompatible with number [2]. [incompatible-call]
[2] 6│ constructor(title: string, year: number) {
:
13│ movie.rating = rating;
14│ }
15│
[1] 16│ const ironMan: Movie = new Movie('Iron Man', '2008');
17│
18│ rateMovie('Iron Man', 4);
19│
Error ----------------------------------------------- index.js:18:11
Cannot call rateMovie with 'Iron Man' bound to movie because string [1] is incompatible with Movie [2].
[incompatible-call]
[2] 12│ function rateMovie(movie: Movie, rating: number) {
13│ movie.rating = rating;
14│ }
15│
16│ const ironMan: Movie = new Movie('Iron Man', '2008');
17│
[1] 18│ rateMovie('Iron Man', 4);
19│
Found 2 errors
If you try to run the source code directly in Node.js, you get a SyntaxError because Flow supports the full JavaScript language standard, but the type markup violates JavaScript syntax. For this reason, you need another tool in addition to Flow that converts the code with the markups back into valid JavaScript code. The simplest solution is to use the flow-remove-types package. You can install this package using the npm install -D flowremove-types command. Typically, for flow files, you should add the .flow.js extension to indicate that these files can’t be executed directly in Node.js. When you save the source code shown earlier in a file named index.flow.js, calling npx flow-remove-types index.flow.js > index.js creates the index.js file, which you can launch via the node index.js command.
Not only can you execute the two commands directly on the command line, but you can also include and combine them in your package.json file. So, the start script shown below combines both commands and runs your application.
{
"name": "node-buch",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"private": true,
"scripts": {
"check": "flow check",
"removeTypes": "flow-remove-types index.flow.js > index.js",
"start": "npm run check && npm run removeTypes && node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"flow-bin": "^0.157.0",
"flow-remove-types": "^2.157.0"
}
}
For the npm start command to run successfully, you must first rename the index.js file to index.flow.js. Then fix the two errors so that the file looks like this.
/* @flow */
class Movie {
title: string;
year: number;
rating: number;
constructor(title: string, year: number) {
this.title = title;
this.year = year;
}
}
function rateMovie(movie: Movie, rating: number) {
movie.rating = rating;
}
const ironMan: Movie = new Movie('Iron Man', 2008);
rateMovie(ironMan, 4);
console.log(ironMan);
If you now run the npm start command, you’ll get an output like the one shown here.
$ npm start
> listing13_2@1.0.0 start
> npm run check && npm run removeTypes && node index.js
> listing13_2@1.0.0 check
> flow check
Found 0 errors
> listing13_2@1.0.0 removeTypes
> flow-remove-types index.flow.js > index.js
Movie { title: 'Iron Man', year: 2008, rating: 4 }
An alternative to using the flow-remove-types package is to use the Babel transpiler.
The difference between Flow and other type systems for JavaScript is that Flow focuses only on checking the data types in the source code. Other tools, such as TypeScript, intervene in the source code and transform it.
TypeScript
Originally developed my Microsoft, TypeScript is a language based on JavaScript that includes a type system as one of its most important features. Like Flow, TypeScript is available as an open-source project on GitHub at https://github.com/Microsoft/TypeScript. The official website can be found at www.typescriptlang.org/. As described earlier, TypeScript is capable of actively modifying the source code of your application. When you use TypeScript, you also don’t write JavaScript source code—you write TypeScript. However, TypeScript is a superset of JavaScript, so valid JavaScript is also valid TypeScript in most cases. The TypeScript source code is then compiled by the TypeScript compiler into JavaScript. In this context, you can configure what the resulting source code should look like. For example, ES3 is supported as a target format, but ES2021 is also supported. Depending on your choice, TypeScript uses polyfills to make new features available in older environments. The newer the ECMAScript version, the smaller the differences between the TypeScript source code and the resulting JavaScript code. However, TypeScript is always adding new features, so you can use them even before they are integrated into the JavaScript language standard. For example, it’s possible to use the ECMAScript module system with import and export already in version 4 of Node.js. However, because Node.js puts your environment much more under your control than the browser does, you can choose a more modern compile target, reducing the overhead generated by TypeScript.
As you can see, the TypeScript source code in the listing below strongly resembles the implementation in Flow. Both type systems use the same solution to annotate types. A variable is followed by the type specification separated by a colon. In TypeScript, you can use types for variables as well as in function signatures.
class Movie {
public rating: number;
constructor(public title: string, public year: number) {}
}
function rateMovie(movie: Movie, rating: number) {
movie.rating = rating;
}
const ironMan: Movie = new Movie('Iron Man', '2008');
rateMovie('Iron Man', 4);
You can install the TypeScript compiler on your system using the package manager of your choice. To install TypeScript locally in your project, you must use the npm install-D typescript command. Again, as with Flow before, you should have a local package. json file where the dependencies are entered correctly. The official documentation recommends the global installation via the npm install -g typescript command because, in this case, you have the TypeScript compiler accessible everywhere on the command line. However, this variant reaches its limits if you need multiple versions of TypeScript on your system. You can launch the TypeScript compiler via the npx tsc command or only tsc if you’ve chosen the global installation. Save the source code from the last listing in a file named index.ts, and use the tsc index.ts command to check and convert the source code. The next listing shows the output of the command.
index.ts:10:46 -
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
10 const ironMan: Movie = new Movie('Iron Man', '2008');
~~~~~~
index.ts:12:11 -
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Movie'.
12 rateMovie('Iron Man', 4);
~~~~~~~~~~
Found 2 errors.
As you can see, the output of TypeScript is a bit shorter than that of Flow. However, it contains all the important information such as file name, exact location of the problem, and a detailed error message. In addition to the console output, the compiler generated the index.js file, which, despite error messages, contains executable JavaScript code that you can run with Node.js.
Duck Typing: TypeScript doesn’t perform type checking strictly, but in a pattern called duck typing. Duck typing means that when you assign a particular class as a type to a variable, the value of that variable doesn’t have to be an instance of that class or of one of its subclasses— it just has to match the structure of the class.
Duck typing derives from a saying that if a bird moves, swims, and quacks like a duck, it’s a duck. The same is true for TypeScript objects and classes. So, if an object has the same properties and methods as the user class, it’s a user type.
Editor’s note: This post has been adapted from a section of the book Node.js: The Comprehensive Guide by Sebastian Springer.
Comments