Blog post

Typing your JavaScript without writing TypeScript

Blog Author Phil Nash

Phil Nash

Developer Advocate JS/TS

3 min read

Get the benefits of TypeScript without writing TypeScript

We recently looked at how you can get some of the benefits of TypeScript in our JavaScript code base. If you've been through that process, then TypeScript is already helping to keep some type issues out of your JavaScript. But, because it is acting on JavaScript, TypeScript will not be able to infer as much as it would like to.


The good news is that we can give TypeScript more hints about the types that flow around our JavaScript application without changing the project to TypeScript.

Adding more types

One way to do this is by using JSDoc. JSDoc provides a way for you to document your code through comments that are also machine-readable. This was initially built to generate API documentation for your code, but TypeScript has adopted it as a valid way to provide type information in JavaScript files.

JSDoc

You can use JSDoc to set the types of variables. In this example, assigning an empty array to the variable names doesn't tell the type system anything, but if we use the JSDoc @type annotation, then we indicate to TypeScript that this variable should only be an array of strings. As this is documentation, it also tells you and your team what kind of variable this is.

/**
 * @type {Array<string>}
 */
let names = [];

If you later try to assign a value with a different type to names or try to add an element of the wrong type, TypeScript will complain about it.

A screenshot of the code above, with the addition of a line where we push a number into the array of names. A TypeScript error is shown in the JavaScript file saying "Argument of type 'number' is not assignable to parameter of type 'string'.

You can also define complex types with @typedef

/**
 * @typedef {object} TeamMember
 * @property {string} name - the full name of a team member
 * @property {Array<string>} languages - the programming languages the member knows
 */

/**
 * @type {TeamMember}
 */
const teamMember = {
  name: "Phil Nash",
  languages: ["JavaScript", "TypeScript", "CSS", "HTML"],
};

With JSDoc annotations in place, you can import types from one file into another, meaning you don't have to repeat yourself and redefine types in multiple places.


You can also type the parameters and the return value of functions:

/**
 * @typedef {import("./teamMember").TeamMember} TeamMember
 * @param {Array<TeamMember>} teamMembers - a list of team members
 * @returns {Array<string>}
 */

function getNames(teamMembers) {
  return teamMembers.map(tm => tm.name);
}
A screenshot of the above code in VS Code. The call to the name property of the tm variable is highlighted and a popover shows TypeScript's understanding of the code. It is shown to be a string and it pulls through the documentation from the JSDoc type definition in another file.

There's more to JSDoc than this, and you can read up on the JSDoc annotations that TypeScript supports in the TypeScript documentation.

TypeScript declaration files

If you find writing type definitions in comments in your files too noisy, you can choose to write those definitions in a declaration file instead. TypeScript declaration files are the .d.ts files you'll find in the Definitely Typed project or in JavaScript libraries that also ship their type definitions.


Writing TypeScript declaration files is the closest this article will get to writing TypeScript itself. A declaration file declares the types of classes, functions and variables in TypeScript syntax without defining the contents.


To use a declaration file to define the TeamMember type, from above, you would create a teamMember.d.ts file and enter the following:

export type TeamMember = {
  name: string;
  languages: Array<string>;
};

You can then import that in your JavaScript using the JSDoc import syntax:

/**
 * @type {import("./teamMember.d").TeamMember}
 */

const teamMember = {
  name: "John Doe",
  languages: ["JavaScript", "TypeScript", "HTML", "CSS"],
};

Importing your types from a declaration file keeps your type definitions separate from your code and gives you all of the expressiveness of the TypeScript type system. Adding declaration files like this to libraries that you write means that other projects can also install the library and access the types.

Still not TypeScript, just supercharged JavaScript

Remember, you're still in a JavaScript project here. You don't need to add these types, but if you find parts of your code that would benefit from them, the combination of JavaScript, JSDoc and, optionally, declaration files means that you can.


Giving more type information will help TypeScript understand your JavaScript better, which in turn helps your IDE to make better suggestions and helps SonarQube and SonarCloud apply TypeScript rules to your source code.


Do watch out, though; after going through this evolution, from adding TypeScript to your JavaScript project and adding more types, you might want to convert your project all the way to TypeScript. Thankfully, if you have got this far, the effort to move is now much lower and, much like adding types to the code base, can be done incrementally.

Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

By submitting this form, you agree to the Privacy Policy and Cookie Policy.

the Sonar solution

SonarLint

Clean Code from the start in your IDE

Up your coding game and discover issues early. SonarLint takes linting to another level empowering you to find & fix issues in real time.

Install SonarLint -->
SonarQube

Clean Code for teams and enterprises

Empower development teams with a self-hosted code quality and security solution that deeply integrates into your enterprise environment; enabling you to deploy clean code consistently and reliably.

Download SonarQube -->
SonarCloud

Clean Code in your cloud workflow

Enable your team to deliver clean code consistently and efficiently with a code review tool that easily integrates into the cloud DevOps platforms and extend your CI/CD workflow.

Try SonarCloud -->