Type-Checking starting from JavaScript to TypeScript

Recently here at Nintex, we saw the need to create a package that can be used across multiple teams for collecting usage analytics. TypeScript was a strong candidate for the language as most teams were already using JavaScript. But, at the same time, we needed a way to enforce the data structure and the types within the payloads being sent to our servers.

As it was a new project, the decision to use TypeScript was very easy to make. Especially when starting fresh. Had it been for an existing project, however, it could’ve been a different story.

Navigating a JavaScript world

As of 2021, JavaScript still dominates GitHub projects. However, for most developers, diving into a JavaScript project with no type-safety in place can feel like navigating into a large field of landmines. A simple task such as calling functions can be prone to runtime issues like parameter type mismatch, incorrect property casing, and missing required arguments.

Discovering these mistakes in production as runtime errors is every team’s nightmare scenario. While unit tests help, the most common problems still require developers to run code just to find bugs.

Fortunately, tooling for JavaScript projects has greatly improved in recent years. The advent of open-source static-type checkers like Flow and static analysis tools like ESLint now provide immediate feedback to developers as they code. A study in 2017 found that 15% of publicly-known bugs could have been prevented by using type-checking (for example, using Flow or TypeScript).

A gradual adoption

TypeScript (which is a superset language of JavaScript) is becoming a more preferred language for developing apps with scale. It provides type-checking for JavaScript files during compile-time and provides semantic analysis for code editors.

While TypeScript allows for gradual adoption in your JavaScript projects, there are still many cases where just using vanilla JavaScript is enough to do the job. These approaches can help your team ease in to a type-safe standard (and benefit from it) without committing to a full-on TypeScript migration.

Here’s a list of strategies to help with type-checking your JavaScript files, starting from using vanilla JavaScript.

Tip 1: Enable checkJS or TS Check on your IDE

Most of today’s popular IDEs for JavaScript development (VS Code, WebStorm, etc.) have built-in type checkers for JS file editors. Switching this on allows your IDE to give you real-time type-checking feedback without having to change too much code and without affecting how your project is compiled.

On VS Code, simply check the check JS checkbox from the settings to enable type-checking for your whole project. If you wish to enable this only for specific files, you can add // @ts-check at the top of those files instead. For more information, check out this VS Code documentation.

Tip 2: Use destructuring

A common problem when dealing with objects in JavaScript is not knowing exactly what properties are inside the object. Consider this example.

If a developer calls this function with a missing key, no issues will be detected by the IDE prior to runtime:

However, this line will cause an exception to be thrown on the first line of readConfig since server is not defined in the parameter.

A simple way to make this clearer for anyone calling readConfig is by destructuring the serverConfig object with the actual properties needed by the function.

Here, it’s clear to the developer that readConfig expects the first parameter to be an object that has a structure like this:

And so when a developer passes an object with one property missing, the IDE shows an error.

This is great — we’re one step closer to preventing bugs before runtime.

Tip 3: Take advantage of type inference

There are still some potential problems in the previous example. What if a developer mistakenly passes a string for server.port?

We only specified the structure in readConfig, but we never specified what types the function expects those properties to be in. The developer calling readConfig doesn’t necessarily know that the port is expected to be of type number. Unfortunately, the above call, while unsafe during runtime, is still valid syntax in the JavaScript world.

One way to remedy this is by assigning default values to your function’s parameters.

By doing this, we’re allowing our type checkers to infer the types of our parameters based on the default values we’ve assigned. Below, you’ll see that our IDE has detected port to be using the wrong type.

Of course, do this only when it makes sense and not because you simply want to introduce type-checking.

Tip 4: Use JSDoc

We obviously can’t set default values to all our parameters in every function. Sometimes, we want these parameters to be explicitly given by the developer.

The simplest way to do this is by adding a JSDoc to our function.

In this example, we got rid of both the destructuring of the config object and its default assignments while maintaining the benefits of implict type checking. The key change here is the function’s JSDoc.

In the screenshot above, notice that our IDE’s type checker still detected this with just a JSDoc.

There are three parts in this JSDoc worth noting:

@param {string} paramName

  1. @param. This is a JSDoc tag to indicate you’re documenting a parameter
  2. {string} The type enclosed in curly braces. In our example above for readConfig, we used a complex type as defined below:

{
server: {
port: number;
},
enabledFeatures: string[]
}

If you’re familiar with TypeScript, you may recognize this syntax immediately. This is how we define types or interfaces in TypeScript. This is the Google Closure Compiler type expression which is based on the EcmaScript 4 spec. For more information on this, check out this documentation from Google.

3.config. The name of the function parameter we’re documenting.

The great thing about using JSDoc is that you can use it almost anywhere in the code and it won’t be included in your production-compiled sources. It adds a lot of information to your code for the type-checker to consume, helping developers get better information around the project without digging too much into the code.

Here’s an example JSDoc for a variable:

Here we created a type definition using the @typedef JSDoc tag to define a complex type called ServerConfig. Then we used that type on our config variable using the @type tag. This will tell our type checkers to ensure that any value assigned to config should match the ServerConfig type definition.

Now let’s try assign an object to config with a missing port:

Our IDE’s type checker immediately detected the missing port for us, even going so far as telling us where in the object it should be and what type it is.

These JSDoc type definitions can easily be migrated to TypeScript. For more information on the types you can use for your JSDocs, see this documentation.

Tip 5: Start integrating TypeScript

If you’ve been doing the above strategies in your JavaScript project and your team’s feeling more confident with declaring and using types, you may want to consider actually migrating your project to TypeScript.

Migrating to TypeScript doesn’t necessarily require you to convert all your .js files to .ts files and declare types to everything immediately. It can be a gradual adoption since TypeScript is interoperable with JavaScript. That means you can have JavaScript files alongside your TypeScript files.

If you’ve been writing JSDocs in your .js files, you’re already halfway to a fully-typed.ts file. Here’s an example file using JSDoc:

Here’s an equivalent in TypeScript:

Notice that the type definition we used in the .js file is exactly the same type definition we have declared in our JSDoc.

If you need more information, TypeScript has some documentation around migration.

Summary

To help with type-checking your JavaScript files, you can use one or more of the strategies below:

  1. Enable your IDE’s type-checking
  2. Use object destructuring, where possible.
  3. Assign default values to benefit from type inference, if it makes sense.
  4. Preferably, use JSDoc as this will help you migrate to TypeScript easier

When you’re ready, consider migrating to TypeScript.

 

 

Interested in exploring more engineering posts from our team here at Nintex? Click here to discover more. To learn more about engineering careers at Nintex, click here.

 

 

Armond Ave

Armond Ave is a Senior Engineer for Nintex based out of Melbourne.

Request a live demo
See how you can manage, automate and optimize your business processes today ‐ get a demo from one of our experts.
Why Our Customers Trust Nintex on

Please wait while form loads...

Couldn't load the form.

Please disable your ad blocker or try a different browser. If you continue to experience issues, please contact info@nintex.com