DEV Community

Cover image for JSDoc Evangelism
Peter Vivo
Peter Vivo

Posted on • Edited on

JSDoc Evangelism

TL;DR

Working with a legacy codebase—something many of us can't avoid from time to time—led me to try JSDoc instead of TypeScript. I have to reveal the surprising truth!

First of all, let's clarify: JSDoc or TS simply means enhancing the developer experience (including later review, reuse, or understanding in any environment: git web page, random editors, Chrome, Firefox DevTools, Vim, cat, etc.).

The First Step

Just open your editor, write a proper comment, and test if JSDoc is working.

/** @typedef { 'JS' | 'JQuery' | 'TS' | 'JSDoc' } Phase; */

/**
 * On typing ' editor will popup string list.
 *
 * @type {Phase}
 */
const badCase = 'React'; // !!!! lint alert !!!!

/** @type {Phase} */
const goodCase = 'JQuery';
Enter fullscreen mode Exit fullscreen mode

JSDoc vs. TypeScript

In my experience, JSDoc can replace TypeScript in many scenarios, with the added bonus of interoperability between the two.

The biggest advantage of JSDoc, in my opinion, is that it uses a standard JS comment system, which means it won't break any JavaScript code and will run everywhere JS can run.

Feature JSDoc TypeScript
Compilation No need to compile the code Mandatory to compile
Dependency Works without any dependencies Requires TypeScript as a dependency
Namespace Doesn't interfere with types and other imports—you can use the same name for components and types in React, for example TypeScript names can conflict with other imports
Rework No need to change existing code, just add a comment Some parts of your code need to be rewritten in TS
Legacy Code Can be used when transforming to TypeScript isn't an option—many legacy projects fall in this category Requires management approval to modify the project
Incremental Adoption You can use JSDoc where you need it—even in new commits only, easily searchable for future changes Can be introduced gradually but still requires modifying build systems
Future Flexibility Can easily be translated to TypeScript later since they use similar under-the-hood mechanics Requires work to revert back to JS if needed
Runtime Expectations Since it's just a comment, it's clear that it doesn't perform runtime type checks, just like TypeScript TypeScript may give the impression of runtime type safety

JSDoc Editor Experience

I can write JSDoc in any editor, but it's rare for editors to fully understand it.

Node Module Experience

I also created an npm module, jsdoc-duck, as a JSDoc-coded module. This highlighted that without TypeScript, creating a JSDoc npm module isn't straightforward. Maybe if I spent more time figuring out Vite build parameters, a solution could be found. But the good news is that you don't need to use that module via npm—you can simply copy index.js to a local place, which avoids adding a new dependency and mitigates any risks if the module owner makes breaking changes.

Wormhole Between JSDoc and TypeScript

The good news is that TypeScript and JSDoc are compatible. JSDoc uses slightly different syntax, but you can use JSDoc module types in TypeScript projects and vice versa. Ultimately, the choice is yours.

Follow the Yellow Brick Road

VS Code is a great example of how well it can show your types in JSDoc code—in my opinion, it's surprisingly low-noise compared to TypeScript.

syntax highlight of JSDoc by VSCode

Bonus

VS-Code Snippets

  "@type": {
    "prefix": ["ty"],
    "body": ["/** @type {$0} */"],
    "description": "jsdoc type"
  },

  "@typedef": {
    "prefix": ["td"],
    "body": ["/** @typedef {$1} Foo */"],
    "description": "jsdoc typedef"
  },
Enter fullscreen mode Exit fullscreen mode

JSDoc-Duck Code Example

In this view, syntax highlighting doesn't help to understand the types. However, this short program is a good example of how JSDoc can use TypeScript's advanced features too.

import { useMemo, useReducer } from "react";

/**
 * @template T - Payload Type
 * @typedef {T extends { type: infer U, payload?: infer P } ? { type: U, payload?: P } : never} ActionType
 */

/** @template AM - Actions Map @typedef {{ [K in AM['type']]: K }} Labels */

/** @template AM - Actions Map @typedef {{ [T in AM["type"]]: Extract<AM, { type: T }> extends { payload: infer P } ? (payload: P) => void : () => void }} Quack */

/**
 * @template ST - State
 * @template AM - Actions Map
 * @typedef {(state: ST, action: AM) => ST} Reducer
 */

/**
 * Factory function to create a typed action map.
 * @template AM - Actions Map
 * @param {Labels<AM>} labelsObject - The keys representing action labels.
 * @param {function} dispatch - The dispatch function for actions.
 * @return {Quack<AM>} The resulting typed action map.
 */
export const quackFactory = (labelsObject, dispatch) => Object
  .keys(labelsObject)
  .reduce(
    /**
     * @arg {Quack<AM>} acc
     * @arg {keyof Labels<AM>} type
     * @return {Quack<AM>}
     */
    (acc, type) => ({
    ...acc,
    [type]: (payload) => {dispatch({ type, payload });}
  }), {});

/**
 * A factory hook to create a state and a typed dispatch functions\
 * @exports useDuck
 * @template AM - Actions Map
 * @template ST - State Typer
 * @param {(st: ST, action: AM) => ST} reducer - The reducer function to manage the state.
 * @param {ST} initialState - The initial state value.
 * @return {[ST, Quack<AM>]} The current state and a map of action dispatch functions.
 */
export const useDuck = (reducer, initialState, labels) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const quack = useMemo(
    () => quackFactory(labels, dispatch),
    [dispatch, labels]
  );
  return ([state, quack]);
};
Enter fullscreen mode Exit fullscreen mode

Happy coding! Borrowing, instead of adding dependencies, can simplify things.

Top comments (5)

Collapse
 
digitaldrreamer profile image
Abdullah Bashir

Nice. These are the reasons I left TS for JsDoc. Simple comments, no transpiling. Thanks.

Seems like English isn't your first language though. You could pass your post through an AI to fix your grammar next time.

Collapse
 
pengeszikra profile image
Peter Vivo

thx, I&AI will be fix the gramar

Collapse
 
pengeszikra profile image
Peter Vivo

fixed

Collapse
 
trplx_gaming profile image
Gabriel Ibe

I'm not against anything that tries to improve upon JS, be it JSDoc or TS

For me personally coming from a strong statically typed language like Java, TS is way easier to transition to.

Well as for JSDoc, I hate comments and seeing that it's mandatory to improve my code, I don't even bother as far as TS does what an average statically typed language does and even more, it's my defacto productivity language 🤓

Collapse
 
pengeszikra profile image
Peter Vivo

I understund your point. My question is how fare need to go with a statically typed program in so dynamical domain as web development. If we just talking about pure JS or TS then it is great area to using type system. But when we are try to work DOM then even the simple document.querySelect return type will be rapidly diverge by type of given tag.

JSDoc technically equivalent with TS just the syntax is bit different, plus JSDoc don't force us.