JSDoc, an alternative to TypeScript?

39168e9b2868d2191e327b7492da3cf2.jpeg

Many developers prefer using TypeScript because of its type checking capabilities. However, this requires an additional translation step, which can cause trouble and waste time. This article will show you how to use JSDoc to get the same type of control, while using pure JavaScript for the fastest development times and better documentation!

JavaScript has solidified its position as one of the most commonly used scripting languages in recent years. It is known for the ease of scripting on the web platform. As the language has evolved, it has evolved from just a “toy” language that leveraged Java’s success to a full language for building more than just small scripts.

4c62d034f99e1678f85955332db52bb4.jpeg

Unfortunately, this reveals a flaw in the language. Some of these include:

  • Lack of static typing and strict type checking: JavaScript is very permissive, allowing parameters to be passed to functions that don’t accept it, required values can be omitted, etc. This is not allowed in statically typed languages because it will cause a compile-time error. These errors will appear in JavaScript applications in production environments.

  • JavaScript has difficulty scaling and maintaining large code bases: JavaScript does not provide powerful mechanisms to manage large code bases, making it challenging to scale and maintain projects over time.

Typescript

In 2014, Microsoft launched TypeScript v1.0. This changes the entire JavaScript ecosystem.

TypeScript is a superset of JavaScript that solves the above problems and more. This has made it increasingly popular in recent times.

The 2022 State of Js survey shows a rise in TypeScript usage.

9e5286bacb8652ec6f2f83634b3e5432.png

While TypeScript solves many problems, it is not without its shortcomings.

In this article, we’ll introduce a great TypeScript alternative called JSDoc that solves the problems of static typing and extensibility while also eliminating some of TypeScript’s shortcomings in the JavaScript ecosystem.

What is JSDoc?

JSDoc is a documentation system for JavaScript. It works by using comments containing JSDoc syntax.

JSDoc syntax serves a variety of purposes, including annotating values with types, specifying parameter and return types for functions, providing documentation and usage information for functions, and type errors. Similar to TypeScript, these can be leveraged by code editors as guides for programmers building, using, or maintaining said code base.

JSDoc VS TypeScript

Both JSDoc and TypeScript solve the problem of writing and maintaining pure JavaScript code. However, they take different approaches, each with pros and cons.

Advantages of JSDoc over TypeScript:

  • Flexibility and compatibility: JSDoc is just a JavaScript annotation, which means it can be added to any JavaScript codebase regardless of language version and is not tied to a compiler like TypeScript.

  • Code comments: JSDoc can be used not only for type checking, but also for adding more documentation, describing how functions work, and generating documentation websites, thus providing value to enhance the maintainability and understandability of your code.

  • No compilation step required: This is one of the most motivating reasons to switch from TypeScript to JSDoc. TypeScript requires compilation to convert TypeScript code into JavaScript so that the browser can understand it, while JSDoc does not require any additional steps because they are just “comments”, a feature supported by JavaScript itself. This simplifies and speeds up the development workflow compared to having to use the necessary TypeScript build process every time a change is made.

Disadvantages of using JSDoc

Although JSDoc has many advantages over TypeScript, the use of TypeScript has become more and more common over time. Here are some advantages of TypeScript over JSDoc:

  • More powerful static typing: TypeScript provides a powerful type model and catches these errors at compile time. Unlike JSDoc, these types end up in the code itself and are not enforced.

  • Type inference: TypeScript can infer types from their values. This helps reduce explicit type annotations, making the code base cleaner.

  • Transpilation: TypeScript can adopt the latest and future features of the JavaScript language through its polyfill feature. It effectively translates this code into a comprehensible version for browsers that don’t yet support these features.

How to use JSDoc: Basics

Due to its long-standing existence, JSDoc is widely supported in all modern editors and can be used out of the box without any installation.

Adding a JSDoc in a .js file is just a comment as described, done by turning on a comment with an extra *

//Normal Javascript Comment 1
/*Normal Javascript Comment 2 */

/**
 JSDoc containing two asterisks
  */

These are some basic features to get started.

Add a code description to the code block:

/** The name of the language JSDoc is written for*/
const language = "JavaScript"

Add a type to the value:

/**
 * This represents the writer of this blog
 * @type {string}
 */
const writerName = "Elijah"

Now, specify that the username variable should be of type string.

Add types to objects and arrays:

/**
* @type {Array<string>}
*/
const colors = ['red', 'blue', 'green']

/**
 * @type {number[]}
 */
const primeNumbers = [1, 2, 3, 5, 7]

Both methods are valid in JSDoc (same as Typescript).

An object type can be created using the @typedef directive.

/**
 * * @typedef {Object} User - A user schema
 * @property {number} id
 * @property {string} username
 * @property {string} email
 * @property {Array<number>} postLikes
 * @property {string[]} friends
 */
/**@type {User} */
const person1 = {
  id: 847,
  username: "Elijah",
  email: "[email protected]",
  postLikes: [44, 22, 24, 39],
  friends: ['fede', 'Elijah']
}
/** @type {User} */
const person2 = {
id: 424,
username: "Winston",
email: "[email protected]",
postLike: [18, 53, 98],
friends: ['Favour', 'Jane']
}

Input function (parameters, return value and expected error type):

/**
 * Divide two numbers.
 * @param {number} dividend - The number to be divided.
 * @param {number} divisor - The number to divide by.
 * @returns {number} The result of the division.
 */
function divideNumbers(dividend, divisor) {
    return dividend/divisor;
}

The keyword @param is followed by a type that represents the value that the defined function will accept. You can also add a parameter description after a hyphen (-).

The keyword @returns is used to define what a function returns. This is especially useful for large functions. It can be difficult to scan all of the code, including early returns, to determine the function’s expected behavior.

Additionally, you can use the @throws directive to add errors that a function may throw.

To improve the division function, we can specify that an error is returned if the divider is zero and handle this case in the code.

/**
 * Divide two numbers.
 * @param {number} dividend - The number to be divided.
 * @param {number} divisor - The number to divide by.
 * @returns {number} The result of the division.
 * @throws {ZeroDivisionError} Argument divisor must be non-zero
 */
function divideNumbers(dividend, divisor) {
    if (divisor === 0) {
        throw new DivisionByZeroError('Cannot Divide by zero')
    }
    return dividend/divisor;
}

@throws can accept an error type (ZeroDivisionError) or a description (argument divisor must…) or both.

/**
 * Custom error for division by zero.
 */
class DivisionByZeroError extends Error {
    constructor(message = "Cannot Divide By Zero") {
        super(message);
      this.name = "DivisionByZeroError";
    }
}

Since JavaScript itself doesn’t force you to handle errors, you must do so because it helps improve collaboration and maintenance.

Enter the complete class (description, constructor and methods)

Going one step further, you can also use JSDoc to enter the complete class syntax.

/**
 * A Rectangle Class
 * @class
 * @classdec A four-sided polygon with opposite sides of equal length and four right angles
 */
class Rectangle {
  /**
   * Initializing a Rectangle object.
   * @param {number} length - The length of the rectangle.
   * @param {number} width - The width of the rectangle.
   */
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }

  /**
   * Calculate the area of the rectangle.
   * @returns {number} The area of the rectangle.
   */
  calculateArea() {
    return this.length * this.width;
  }

  /**
   * Calculate the perimeter of the rectangle.
   * @returns {number} The perimeter of the rectangle.
   */
  calculatePerimeter() {
    return 2 * (this.length + this.width);
  }
}

Above is a simple rectangle class with two methods to calculate its area and perimeter.

The @class keyword is used to represent functions that need to be called using the new keyword. @classdec is used to describe the entire class. When writing a class, it’s important to refine it further by adding types and descriptions.

  • Constructor

  • All methods and variables created in the class

We use the @params keyword to provide the type and description of the parameters that need to be passed to the constructor. Methods in classes are typed in the same way as functions, which was introduced in the previous section.

Improve common code documentation:

In addition to adding necessary types to your code, JSDoc has many ways to improve readability and ease of understanding. Here are a few of them:

Add code author: You can use the @author directive to add the author of the project, including the author’s name and email

/**
     *Possible title for this article
    * @type {string}
    * @author Elijah [[email protected]]
    */
const articleTitle = "Demystifying JSDoc"

You can also add code snippets to show how specific blocks of code are used. This is especially useful for complex blocks of code.

/**
 * Sums of the square of two numbers a**2 + b**2
 * @example <caption>How to use the sumSquares function</caption>
 * // returns 13
 *sumSquares(2, 3)
 * @example
 * // returns 41
 *sumSquares(4, 5)
 * // Typing the function
 * @param {number} a - The first number
 * @param {number} b - The second number
 * @returns {Number} Returns the sum of the squares
 * */
const sumSquares = function(a, b){
    return a**2 + b**2
}

We use the @example directive to achieve this. This can also be annotated using the caption tag.

Version control: You can also specify the version of your project using the @version directive.

/**
 * @version 1.0.0
 * @type {number}
 * */
const meaningOfLife = 42

Useful links: Often, you may want to point users elsewhere so they can gain more knowledge about your code. This could be a GitHub repository, some tutorials, a blog, etc. To achieve this, there are two directives that can help. @link and @tutorial .

/**
 * How to use the link tags
 * Also see the {@link https://jsdoc.app/tags-inline-link.html official docs} for more information
 * @tutorial getting-started
 * */
function myFunction (){
}

The @link tag renders official docs as a link to the specified link. It is used to create a link to a specified URL, whereas the @tutorial tag is used to direct the user to a relative tutorial link in the generated documentation.

Create a module: Create a module in JSDoc using the @module tag at the top of the file. This will make the current file a module. Modules will be grouped in separate sections on the generated documentation site.

// jsdoc.js
/** @module firstDoc */
//The rest of the code goes here

Convert JSDoc files

One of the biggest advantages of using JSDoc is the ability to convert JSDoc files to a documentation website or even to Typescript in order to enjoy the benefits of using Typescript such as catching errors at compile time, integrating with Typescript projects, etc.

Generate documentation website from JSDoc files

As mentioned above, follow these steps to create a more readable GUI:

Install jsdoc

npm install -g jsdoc

Run jsdoc to get the target file

jsdoc path/to/file.js

8f7ef50f551e3a1c64778bc307269b3e.png

This is what the default generated template looks like, but you can configure the appearance of the template.

Generate .d.ts files from JSDoc

In TypeScript, a .d.ts file represents a file containing type declarations that all .ts files can access. You can generate these files from JSDoc code by following these steps:

Install tsd-jsdoc in the project folder

npm install tsd-jsdoc

Generate .d.ts file

for a separate file

jsdoc -t node_modules/tsd-jsdoc/dist -r our/jsdoc/file/path.js

for multiple files

jsdoc -t node_modules/tsd-jsdoc/dist -r file1.js file2.js file3.js ...

for the entire folder

jsdoc -t node_modules/tsd-jsdoc/dist -r src

It combines all types files into one out/types.d.ts file.

Note: This assumes you have installed jsdoc from the previous section. If not, install it before running this step.

Conclusion

So far, we’ve learned the basics of using JSDoc, as well as generating types and documentation sites from JSDoc code. JSDoc is particularly useful in situations when your Typescript compile time/build steps have the opposite impact on productivity, and when dealing with legacy code bases.

Rich Harris (creator of Svelte and SvelteKit) moved the entire Svelte and SvelteKit repository from TypeScript to JSDoc. TypeScript has also added support for many JSDoc declarations (source).

Due to the limited space of the article, today’s content will be shared here. At the end of the article, I would like to remind you that the creation of articles is not easy. If you like my sharing, please don’t forget to like and forward it to let more people in need See. At the same time, if you want to gain more knowledge about front-end technology, please follow me. Your support will be my biggest motivation for sharing. I will continue to output more content, so stay tuned.