DEV Community

Cover image for Throw away the "Script" from "Type""Script".
Yeom suyun
Yeom suyun

Posted on • Updated on

Throw away the "Script" from "Type""Script".

Before writing this article, I will first attach a post that was posted on a community a few days ago.
Turbo 8 is dropping TypeScript
After seeing the title of the above article, I thought it was about the library turbo8 migrating from TypeScript to JSDoc.
However, it was not.
The team removed the type annotations from their codebase, changed the file extensions to js, but did not add comments.
Yes, what they wanted was freedom from types.
At this point, I will attach the post that was posted the day after the above post.
Open source hooliganism and the TypeScript meltdown

TypeScript is not productive.

I mostly agree with DHH's post, and I think that some of the older JavaScript-based libraries that support @types are supporting TypeScript simply because they don't have the courage to stand up to the TypeScript zealots.
There are many cases where libraries support @types because TypeScript users demand it, even if they think TypeScript is unproductive.
However, I am very positive about types.
I am a fan of the JSDoc tool, to be exact.
Therefore, in this article, I will introduce how convenient JSDoc is.

JSDoc's type is just a comments.

JSDoc is a tool that provides type hints for JavaScript using comments.
Some TypeScript programmers criticize this, arguing that comments should only be comments and should not affect code.
However, TypeScript does not have runtime type checking, and JSDoc types are only used for IntelliSense, linting, and code summarization.
Therefore, comments are a very appropriate place to write types.
JSDoc does not affect the source code in any way. It also skips the unnecessary compilation step of TypeScript, allowing you to enjoy the freedom of JavaScript to the fullest.
Of course, there are a few exceptions.
For example, if the type is incorrectly declared or shadowing is attempted, I had to use @ts-ignore to work around the issue.
I have never studied TypeScript separately, so there may be other elegant solutions that I am not aware of.

/// lib.es5.d.ts
charCodeAt(index: number): number;
Enter fullscreen mode Exit fullscreen mode
/// js-source
// @ts-ignore: index -> index?
const code = str.charCodeAt()
Enter fullscreen mode Exit fullscreen mode
/** @type {RegExp} */// @ts-ignore: string -> RegExp
let regex = ".[:=]."
// @ts-ignore: string -> RegExp
for (let key of map.keys())  += "|" + key
regex = RE(regex)
Enter fullscreen mode Exit fullscreen mode

Do types make JavaScript harder?

Let's reverse the question.
How can types make JavaScript difficult?
Could it be a long learning curve, adding verbose syntax, or difficult types?
Here, it seems that the only thing that can be established for JSDoc is difficult types.
I had no problem using JSDoc even with a basic knowledge of Java and a quick glance at a Svelte codebase written in JSDoc.

1. How to set up JSDoc

For more information, please see my article titled How to configure JSDoc instead of TypeScript.
I have made several improvements to make it possible to apply JSDoc to library code concisely and to use it in both JS and TS projects.

2. Applying types to code with JSDoc

This is also all covered in the TypeScript - JSDoc Reference.
The important points can be summarized as follows.

/**
 * @template T
 * @param {T|import("../../private.js").SomeType} my_parameter
 * @returns {Map<T, number>}
 */
function my_func(my_parameter) {
  /** @type {[T, number][]} */
  const array = [
    [/** @type {T} */(my_parameter), 1]
  ]
  const my_map = new Map(array)
  return my_map
}
Enter fullscreen mode Exit fullscreen mode

TypeScript has excellent type inference, so JSDoc only requires a few additional syntaxes.
Here is an example of my actual code.

/**
 * @param {Map<*[], Function>} nodes
 * @param {number} index
 */
const run_dag = (nodes, index) =>
  new Promise(
    (resolve, reject) => {
      /** @type {Map<*[], Function>} */
      const jobs = new Map()
      /** @type {Map<Function, *[][]>} */
      const dependents = new Map()
      /** @type {[number]} */
      const count = [ nodes.size ]

      for (const [dependencies, callback] of nodes) {
        const clone = [...dependencies]
        jobs.set(clone, callback)
        for (const p of clone)
          if (typeof p == "function") {
            const array = dependents.get(p)
            if (array) array.push(clone)
            else dependents.set(p, [ clone ])
          }
      }
      for (const [dependencies, callback] of jobs)
        if (
          dependencies.every(p => typeof p != "function")
        ) {
          run_node(resolve, reject, jobs, dependents, count, dependencies, callback, index)
            .catch(reject)
        }
    }
  )
Enter fullscreen mode Exit fullscreen mode

JSDoc is used in the above code to specify the types of parameters, the generic type of a Map object, and the length of a number[] used as a pointer.
The rest of the code is typed with type inference.
JSDoc also supports auto-completion, making it easy to write function parameters.
JSDoc auto-completion

How to write complex and challenging types

So, there is one reason left why we should not use JSDoc.
The reason why DHH said "Things that should be easy become hard, and things that are hard become any. No thanks!" is probably because defining complex and challenging types is literally complex and challenging.
Adding types to JavaScript provides several improvements to the developer experience, such as autocompletion support and better syntax highlighting.
However, in the case of complex types, typing can be cumbersome, and features like type checking may not work properly.
I will use the types of eslint that I recently worked on as an example.

export interface Property extends BaseNode {
    type: 'Property';
    key: Expression | PrivateIdentifier;
    value: Expression | Pattern; // Could be an AssignmentProperty
    kind: 'init' | 'get' | 'set';
    method: boolean;
    shorthand: boolean;
    computed: boolean;
}
export interface NodeMap {
    AssignmentProperty: AssignmentProperty;
    CatchClause: CatchClause;
    ...
    Property: Property;
    ...
}
type Node = NodeMap[keyof NodeMap];
interface Identifier extends BaseNode, BaseExpression, BasePattern {
    type: 'Identifier';
    name: string;
}
interface NodeParentExtension {
    parent: Node;
}
Identifier: (node: Identifier & NodeParentExtension) => void
Enter fullscreen mode Exit fullscreen mode
Identifier(node) {
  const parent = node.parent
  const type = parent.type
  if ("Property" == type) {
    if (parent.value == node) {
      if (parent.key.name == parent.value.name) { // TS error here
...
}
Enter fullscreen mode Exit fullscreen mode

eslint's Rule.RuleListener.Identifier generates a lot of type errors in normal JS code, making autocompletion impossible.
This is despite the incredibly verbose and detailed d.ts files of eslint and estree.
My initial solution was to make things that are hard become any, as shown below.

/// private.d.ts
interface ASTNode extends Node, Pattern {
  computed: boolean
  id: ASTNode
  imported: ASTNode
  key: ASTNode
  left: ASTNode
  local: ASTNode
  name: string
  object: ASTNode
  parent: ASTNode
  property: ASTNode
  range: [number, number]
  right: ASTNode
  shorthand: boolean
  type: string
  value: ASTNode
}
Enter fullscreen mode Exit fullscreen mode
/** @param {import("../private").ASTNode} node */
Identifier(node) {
...
}
Enter fullscreen mode Exit fullscreen mode

It was a "No Thanks" solution, but I finished coding by fixing type errors and getting some Intellisense support.
After that, I considered how to use the verbose types of estree, and the result is as follows.

/// private.d.ts
export type ASTNode = {
  end: number
  parent: ASTNode
  range: [number, number]
  start: number
} & (
  estree.ArrayExpression
  | estree.ArrayPattern
  | estree.ArrowFunctionExpression
  | estree.AssignmentExpression
  | estree.AssignmentPattern
  | estree.AwaitExpression
  | estree.BinaryExpression
  | estree.BlockStatement
  | estree.BreakStatement
  | estree.CallExpression
  | estree.CatchClause
  | estree.ChainExpression
  | estree.ClassBody
  | estree.ClassDeclaration
  | estree.ClassExpression
  | estree.ConditionalExpression
  | estree.ContinueStatement
  | estree.DebuggerStatement
  | estree.DoWhileStatement
  | estree.EmptyStatement
  | estree.ExportAllDeclaration
  | estree.ExportDefaultDeclaration
  | estree.ExportNamedDeclaration
  | estree.ExportSpecifier
  | estree.ExpressionStatement
  | estree.ForInStatement
  | estree.ForOfStatement
  | estree.ForStatement
  | estree.FunctionDeclaration
  | estree.FunctionExpression
  | estree.Identifier
  | estree.IfStatement
  | estree.ImportDeclaration
  | estree.ImportDefaultSpecifier
  | estree.ImportExpression
  | estree.ImportNamespaceSpecifier
  | estree.ImportSpecifier
  | estree.LabeledStatement
  | estree.Literal
  | estree.LogicalExpression
  | estree.MemberExpression
  | estree.MetaProperty
  | estree.MethodDefinition
  | estree.NewExpression
  | estree.ObjectExpression
  | estree.ObjectPattern
  | estree.PrivateIdentifier
  | estree.Program
  | estree.Property & { key: estree.Identifier }
  | estree.PropertyDefinition
  | estree.RestElement
  | estree.ReturnStatement
  | estree.SequenceExpression
  | estree.SpreadElement
  | estree.StaticBlock
  | estree.Super
  | estree.SwitchCase
  | estree.SwitchStatement
  | estree.TaggedTemplateExpression
  | estree.TemplateElement
  | estree.TemplateLiteral
  | estree.ThisExpression
  | estree.ThrowStatement
  | estree.TryStatement
  | estree.UnaryExpression
  | estree.UpdateExpression
  | estree.VariableDeclaration
  | estree.VariableDeclarator
  | estree.WhileStatement
  | estree.WithStatement
  | estree.YieldExpression
)
Enter fullscreen mode Exit fullscreen mode
/** @param {import("../private").ASTNode & import("estree").Identifier} node */
Identifier(node) {
...
}
Enter fullscreen mode Exit fullscreen mode

The second solution works amazingly well, and I was able to find and fix errors in my first solution's code using typecheck.
The type error was caused by incorrect NodeMap, Identifier, and NodeParentExtension types.
I think that almost all of the unpleasantness of using JSDoc comes from incorrect type declarations.
However, I think it would be difficult to have incorrect type declarations if you coded using JSDoc from the beginning.
This is because you can use JS code as a type directly.

Getting the most out of type inference

JavaScript has implicit types, which allow TypeScript to perform type inference.
We can use JavaScript's basic types without declaring separate types.

export const number = 0 // number
export const string_array = ["string"] // string[]
export const object = { key: "Hello" } // { key: string }
export const map = new Map // Map<any, any>
export class Class {} // class Class
export function func() { return new Set } // function (): Set<any>
Enter fullscreen mode Exit fullscreen mode

Of course, it is also possible to use the inferred types from JavaScript.

import * as js from "./export.js"

typeof js.number // number
typeof js.string_array // string[]
typeof js.object // { key: string }
typeof js.map // Map<any, any>
new js.Class // Class
typeof js.func // function (): Set<any>
ReturnType<typeof js.func> // Set<any>
Enter fullscreen mode Exit fullscreen mode

As you can see from the above examples, TypeScript is able to infer the complex types that occur in JavaScript, so we can simply use them.

Conclusion

Are you using TypeScript?
Use JSDoc.
Are you using JavaScript?
Use JSDoc.
Does TypeScript's unnecessary compilation harm the developer experience?
Use JSDoc.
Does JavaScript's lack of IntelliSense harm the developer experience?
Use JSDoc.
Does TypeScript's typecheck harm JavaScript's freedom?
Make use of type inference.
Is it cumbersome to write d.ts to support TypeScript for a JavaScript library?
Use JSDoc to generate it automatically.

Thank you.

Top comments (91)

Collapse
 
mellis481 profile image
Mike E • Edited

"Freedom from types" = "freedom from seatbelts".

Simply horrible advice.

Collapse
 
artxe2 profile image
Yeom suyun

JSDoc provides perfect type safety.
It simply eliminates the unnecessary compile step by moving the location of Type to comments.
This comment seems to be about the changes in Turbo 8.
However, it is possible that they feel their productivity is improved when using JavaScript without type checking, as they mainly use Ruby, a dynamic type language.
Of course, the attitude of ignoring open source contributors is not at all positive.
However, I think the terrorist-like attack they received is too excessive considering that they successfully developed Ruby on Rails.

Collapse
 
mellis481 profile image
Mike E

LOL. Saying people are like terrorists when they take issue with others denouncing Typescript is a laughable exaggeration.

As for type safety with Jsdoc, it's so utterly painful and insufficient, it should hardly be considered an alternative.

Thread Thread
 
artxe2 profile image
Yeom suyun

If you see this PR, don't you think you can agree with the wording that it is like a terrorist?
I think this PR is definitely in the realm of humor.
Why do you think JSDoc is insufficient?
JSDoc type checking is done through TypeScript.

Thread Thread
 
archie9608 profile image
Archie • Edited

Since you've mentioned in another reply chain that you're not a native speaker, I'd like to help out a bit.

The PR that you've labelled as "terrorist-like" doesn't fit the bill for terrorist-like behaviour. In this context, terrorist-like behaviour would be to create an unsuspecting PR containing modifications that would brick the codebase, instead of openly highlighting the intention of said PR. The difference is that terrorist actions are carried out to incite fear, not come off as goofy actions of someone whining about their toys being taken away. You could certainly call that PR "extremely silly" or "unbelievably childish" if you prefer being hyperbolic about it. Calling it terrorist-like not only paints the person in an unreasonably negative light to unsuspecting readers, but also makes you look really petty to those who bother looking at the PR you refer to.

Other than that, I don't really have a dog in this fight. It's been interesting looking at all this.

Edit: Grammar check

Thread Thread
 
artxe2 profile image
Yeom suyun

I understand. I wanted to express illogical violence, but I think it would have been more appropriate to use the expressions you mentioned as examples.
Thank you for your help.

Collapse
 
tsolan profile image
Eugene

JSDoc is ugly and verbose as much as it’s even possible. I’d rather use wasm then writing such a mess.

Collapse
 
rodiongork profile image
Rodion Gorkovenko

Mike, it quite depends :)

I'm Go and previously Java programmer at work, and I write large enterprise projects there...

However when I code something to myself, I do it in plain PHP, plain JS, Python and Lua generally. Sometimes there arise bugs due to type mistakes, that's true, but for single dev with some experience it is a preferable tradeoff compared to supporting cumbersome and monstrous type-safe code.

And I dare say, some of my projects live for years while some of enterprises I worked for ceased to exist :)))

Collapse
 
freibuis profile image
Freibuis

Having seat belt doesn't make you a 100% safe either. Driving at 200k/h and hit a brick wall and you will change your mind.

When all you will need to is slow down.

I'm on the fence types/non types.

Since I'm currently working on a rails project that has a huge ts presence. I'm kinda looking to be able to ditch the node_modules directory when turbo8 hits. In our case dropping ts has a huge benefit. Our build pipelines drop and other things

That saying, if I was working on a rails API with an react app. Then yerp. TS all the way. I would agree with you 100%

I think turbo8 should drop ts. But should have used jsdoc before doing this

Collapse
 
nosknut profile image
nosknut • Edited

I have worked on many 10k+ line projects the past 6 years, and the only time typescript has presented issues is when trying to use libraries that dont have typings og bad typings ... The compile step was also never an issue. Most frameworks alredy kinda have one through rollup and webpack anyway. The type system has never given me more challenge than i set myself up for. If i decided to do it the hard way for some kind of crazy auto mapped tooling obviously the level of difficulty increases, but if you are unhappy with that and going to delete the types anyway just take the easier routes. Quite frankly this movement is weird and i would like to see some more specific examples of why people are upset. All i hear is the generic compile step excuse but it never was a problem for me until i heavily abused the type system.

Collapse
 
artxe2 profile image
Yeom suyun

only time typescript has presented issues is when trying to use libraries that dont have typings or bad typings

I very much agree with this opinion, and I think that the correct direction for Turbo 8 was to adopt JSDoc.
The compilation of TypeScript is probably not a big problem when used in a project.
However, the library code tends to become inefficient after passing through tsc, and the important thing is that the IDE's Go To Source Definition function does not work properly.

Collapse
 
brense profile image
Rense Bakker

How would going through tsc make the code inefficient? All it does is remove the type annotations, since the rest is already js syntax and can be interpreted by js runtimes without issues.

Thread Thread
 
artxe2 profile image
Yeom suyun

I think my comment was a bit wrong.
Excluding enums and a few other syntaxes, the tsc of TypeScript written in ES6 syntax is the same as JavaScript, as you said.
The fact that a pure JavaScript file can be run by simply changing the extension to ts and using ts-node -T to support your argument.
The limitations of some type guards and the cases where @ts-ignore is used in the document are not relevant because they are problems that also apply to JSDoc, not just TypeScript.

Collapse
 
nosknut profile image
nosknut

I'm pretty sure tsc allows you to target a specific ES version to be compatible with older browsers. Meaning yes it becomes less efficient if you have it set to older ones but if you don't im pretty sure tsc will only remove the typings. The rest of the changes should be done by webpack or rollup no (which you would still use even without typescript)?

Thread Thread
 
artxe2 profile image
Yeom suyun

The speed of tsc is not a problem.
If you use a framework like Vite anyway, won't you eventually get the same result?
The biggest issue is the library codebase.

Thread Thread
 
nosknut profile image
nosknut

Not for the user of the lib. If you simply export .d.ts files the goto functionality will fail in most IDE's and you have to go to the internet for documentation instead of getting linked directly to the code.

Collapse
 
nosknut profile image
nosknut • Edited

What do you mean by inefficient?
Also the Go To Source function only fails for libraries that are implemented with bad typings i.e. not developed in typescript but instead simply publish .d.ts files.
In most libraries the Go To Source will work just fine.
In many ways libraries that switch to tsdoc will make typescript harder to use exactly because it will only generate d.ts files and the Go To Source functionality will only lead to a typedef file.

Thread Thread
 
artxe2 profile image
Yeom suyun

As you said, this is about libraries that are written in JavaScript and have separate d.ts files, or that are written in TypeScript but provide JavaScript result and d.ts files.

Thread Thread
 
nosknut profile image
nosknut

That would mean the correct direction for Turbo8 was not using JSDoc because it makes the typescript developer experience worse ... It sounds to me like they are creating their own problem by doing it.

Thread Thread
 
artxe2 profile image
Yeom suyun

They don't use JSDoc.

Collapse
 
dbarjs profile image
Eduardo Barros

Turbo 8 is not an "freedom" library and the owners don't use JavaScript.

Dont waste your time thinking solutions for this lib.

Collapse
 
bwca profile image
Volodymyr Yepishev

I knew I'd find someone to ask 😁

What are JSDoc capabilities for genetics? I.e. what would be the proper way to convert the following generic type into JSDoc annotations?

type DeepReadonly<T> = Readonly<{
  [K in keyof T]: 
    // Is it a primitive? Then make it readonly
    T[K] extends (number | string | symbol) ? Readonly<T[K]> 
    // Is it an array of items? Then make the array readonly and the item as well
    : T[K] extends Array<infer A> ? Readonly<Array<DeepReadonly<A>>> 
    // It is some other object, make it readonly as well
    : DeepReadonly<T[K]>;
}>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
artxe2 profile image
Yeom suyun

I'm not sure what the exact intention of the question is. In JSDoc, generic types can be used with the @template keyword.

/**
 * @template T
 * @typedef {Readonly<{
 *   [K in keyof T]: T[K] extends (number | string | symbol)
 *     ? Readonly<T[K]>
 *     : T[K] extends Array<infer A>
 *       ? Readonly<Array<DeepReadonly<A>>>
 *       : DeepReadonly<T[K]>;
 * }>} DeepReadonly
 */

/**
 * @typedef State
 * @property {string} string
 * @property {number} number
 * @property {string[]} array
 */

/** @type {DeepReadonly<State>} */
const readonly_state = {
    string: "Hello",
    number: 1234,
    array: ["AB", "CD", "EFG"],
}

readonly_state.string = "Goodbye" // TS error here
Enter fullscreen mode Exit fullscreen mode

However, d.ts files are more convenient and readable than @typedefs.
When writing library code, you need to generate a d.ts file, and TypeScript's d.ts file generation function does not properly support @template.
Therefore, it is recommended to use d.ts files for these type declarations.
@template is used to apply generic types to functions.

Collapse
 
bwca profile image
Volodymyr Yepishev

But you're still using Typescript, but inside JSDoc, which does not answer the original question how to achieve same effect using JSDoc alone.

The aforementioned ts generic marks all nested fields as readonly, as can be seen in the playground.

Suppose we want this behavior, but we are dropping typescript, how should we rewrite it? Keeping ts files goes against the whole idea of using JSDoc instead of typescript, since you're ending up using both.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Keeping ts files goes against the whole idea of using JSDoc instead of typescript, since you're ending up using both.

Not really. The idea is to blaze ahead while TypeScript isn't getting in your way and only catch up on your types when you choose to.

If you are already using a build tool chain this is easily accomplished by using esbuild which doesn't typecheck at all but outputs JS 20-30x faster than tsc can (standard behaviour when using Vite.js in development mode; if you are using an editor with LSP support many type issues will be flagged at author time anyway but with no-typecheck transpilation you don't need to address them RIGHT NOW, THIS MOMENT just to keep tsc happy).

If you want full control of the runtime JS code:

  • Author complex types in a types.ts file in typescript.
  • When needed import types (perhaps conbined with @typedef) into the *.js file and use them as needed with binding JSDoc syntax.
  • By not manually authoring the index.d.ts TypeScript can still be used to generate it.

With this approach you have 100% control over the runtime JavaScript and can adopt TypeScript in the same manner as ESLint—when you are ready, not when TypeScript demands it (i.e. use TypeScript as a type-linter).

The one downside of this approach:

(Since then I've started using “TS JSDoc” instead of “JSDoc TS” because JSDoc is not the primary concern but its only used as the glue between TypeScript types and Vanilla JavaScript code.)

Thread Thread
 
bwca profile image
Volodymyr Yepishev

I see, thanks for clarification. Somehow from the original article I got the impression that JSDoc allows eliminating Typescript altogether from the codebase, so got curious if it is as powerful when it comes to certain features.

Thread Thread
 
artxe2 profile image
Yeom suyun

Complex types are recommended to be defined in a d.ts file, but I think my first comment was a good answer to your question of "How well does TypeScript support JSDoc?"
Generic types can be written using @template, and JSDoc works the same way as TypeScript when opening curly braces and writing types, so if you move all the code in the playground to JSDoc, it will be in the following form.

/**
 * @typedef Person
 * @property {string} firstName
 * @property {string} lastName
 * @property {number} age
 * @property {Person[]} [children]
 */

/**
 * @typedef Family
 * @property {Person[]} parents
 * @property {{
 *   paternal: Person[]
 *   maternal: Person[]
 * }} grandparents
 */

/**
 * @template T
 * @typedef {Readonly<{
 *   [K in keyof T]: 
 *     T[K] extends (number | string | symbol)
 *       ? Readonly<T[K]> 
 *       : T[K] extends Array<infer A>
 *         ? Readonly<Array<DeepReadonly<A>>> 
 *         : DeepReadonly<T[K]>
 * }>} DeepReadonly
 */

/** @type {Family} */
const family = {
  parents: [
    { firstName: "John", lastName: "Doe", age: 40 },
    { firstName: "Jane", lastName: "Doe", age: 38 },
  ],
  grandparents: {
    paternal: [
      { firstName: "PaternalGrandfather", lastName: "Doe", age: 70 },
      { firstName: "PaternalGrandmother", lastName: "Doe", age: 68 },
    ],
    maternal: [
      { firstName: "MaternalGrandfather", lastName: "Smith", age: 75 },
      { firstName: "MaternalGrandmother", lastName: "Smith", age: 72 },
    ],
  }
};

/** @type {DeepReadonly<Family>} */
const family2 = {
    parents: [
      { firstName: "John", lastName: "Doe", age: 40 },
      { firstName: "Jane", lastName: "Doe", age: 38 },
    ],
    grandparents: {
      paternal: [
        { firstName: "PaternalGrandfather", lastName: "Doe", age: 70 },
        { firstName: "PaternalGrandmother", lastName: "Doe", age: 68 },
      ],
      maternal: [
        { firstName: "MaternalGrandfather", lastName: "Smith", age: 75 },
        { firstName: "MaternalGrandmother", lastName: "Smith", age: 72 },
      ],
    }
};

family.parents = []; // ok
family2.parents = []; // error

family.parents[0].age = 1; // ok
family2.parents[0].age = 1; // error

// error
family2.parents.push({
  age: 40,
  firstName: 'Joseph',
  lastName: 'Doe'
});

/** @type {DeepReadonly<Family>} */
const family3 = family;
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
bwca profile image
Volodymyr Yepishev

Fair enough, thanks! :)

Thread Thread
 
brense profile image
Rense Bakker

@peerreynders funny that you bring up esbuild and vite, as both can run typescript code...

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Yes and that was exactly what I was eluding to.

In some way moving to TS JSDoc is trying to get away from TypeScript being a showstopper and more of a development aid (in the capacity of a type linter).

Application developers who heavily depend on pre-typed dependencies just don't seem to “get it”.

And even when you are authoring applications where you try to minimize dependencies you can easily get into “library style” code where TS can be pain if you just want to quickly explore some solutions. Tools like esbuild and Vite.js can help here while you can still code in TypeScript (while ignoring the LSP red squiggles for now). But not everybody is comfortable with that approach.

And before somebody comes in about Deno:

Therefore, by default, TypeScript modules are not type-checked before they are executed.

From that perspective "TypeScript is pure overhead"—at runtime.

There are other reasons for wanting complete control over the runtime JavaScript; with TS JSDoc you can write just JavaScript but keep TypeScript types while trading off some developer convenience.

Thread Thread
 
brense profile image
Rense Bakker

True, i don't get why people want to see typescript as a show stopper... imho it's because they are inexperienced with typed languages and thus don't understand how to set it up? 🤷 I don't know, I don't remember typescript ever being a showstopper in any of my projects. But I get why some libraries move away from using typescript directly... having to write typescript is appearantly a show stopper for some people and if you are a library maintainer, you don't want to discourage those people from making pull requests because they're convinced typescript stops their show.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

having to write typescript is apparently a show stopper for some people and if you are a library maintainer,

That's not it at all, it's not a “skill issue” especially given the fact that maintaining a TS JSDoc code base typically requires a higher TS skill level than plumbing together an app with TypeScript.

It's about a workflow where “eventual typing” is highly valued (TypeScript is a Subset of JavaScript). Unlike Rust's borrow checker, TypeScript's error messages aren't all that helpful trying to write the “right” code.

When you spike a solution, types like:

github.com/GoogleChromeLabs/comlin...

github.com/ReactiveX/rxjs/blob/9aa...

are just way too much effort (right then and there) just to shut up tsc, especially if later that direction doesn't pan out.

On the flip side TS JSDoc also works well for a type first approach which is generally frowned upon by mainstream TS developers (who prefer to let inference do most of the work).

The various types are designed first and captured in *.ts files and then later referenced in the *.js files via JSDoc to ensure that type linting will flag any errors as close to the source as possible (that and TypeScript always infers an array, never a tuple).

Thread Thread
 
brense profile image
Rense Bakker

Typescript is a superset of JavaScript, not a subset.

You keep talking about shutting up tsc, but I am not sure how the examples you give are supposed to be related to that. If you wanted proper typing with jsdoc, you'd have to write your types like that as well...

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Typescript is a superset of JavaScript, not a subset.

Read the link.

TypeScript cannot type all the stunts you can pull off with JavaScript that work perfectly fine at runtime. At that point it becomes necessary to immediately change the code so that it can be typed and understood by tsc rather than deferring that activity to refactoring later during the make it right phase.

And even when the code can be typed, it can be extremely verbose (i.e. complex) to explain it up front to tsc.

you'd have to write your types like that as well...

TS JSDoc isn't about avoiding types but gaining the freedom to choose when to deal with types. The code can be run and even tested before you have to worry about what tsc has to say about it.

Destroy All Software: Ideology


“I often wish I worked in a language where I could start dynamic and end statically.”

Thread Thread
 
brense profile image
Rense Bakker

So don't explain it upfront to tsc? No part of typescript forces you to write proper types. It's an opt in language not an opt out. If you don't want to write proper types, you don't write proper types for that part of your program... 🤷

Collapse
 
endymion1818 profile image
Ben Read

This is a hot topic right now and bound to cause some controversy on both sides of the camp. I started using JSDoc recently and I am getting comfortable with it, however I find the documentation challenging and the lack of tutorials frustrating (try searching for how to correctly type React state using JSDoc).

Collapse
 
artxe2 profile image
Yeom suyun

I'm surprised that I didn't consider JSX at all when writing this article.
After seeing this comment, I ran npx create-next-app@latest and finished the basic setup.
Can you tell me what problems I'm having?

Collapse
 
endymion1818 profile image
Ben Read

Mostly around the useState hook. from memory, I can’t call a variable from state without eslint complaining that the type isn’t a valid setStateAction. IIRC I’m using eslint-plugin-jsdoc with mostly default settings. I’ll double check when I have access to my computer.

Thread Thread
 
artxe2 profile image
Yeom suyun

Can the issue be solved by using type casting on the useState parameter?

"use client"

import { useState } from "react"

export default function Counter() {
  const [ value, set_value ] = useState(/** @type {{ count?: number, str?: string }} */({}))
  return <>
    <button onClick={() => {
      set_value({
        count: (value.count || 0) + 1,
        str: (value.str || "") + "!"
      })
    }}>count: {value.count}</button>
    <br />
    <span>str: {value.str}</span>
  </>
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
endymion1818 profile image
Ben Read • Edited

Nope, still getting

Argument of type '{ isValid: boolean; feedbackMessage: any; }' is not assignable to parameter of type 'SetStateAction<null>'.
  Object literal may only specify known properties, and 'isValid' does not exist in type '(prevState: null) => null'
Enter fullscreen mode Exit fullscreen mode

Unfortunately for my validation to work the value must initially be null

Thread Thread
 
artxe2 profile image
Yeom suyun

If you want to use null, you should write it as follows.

  const [ value, set_value ] = useState(
    /** @type {{ isValid: boolean, feedbackMessage: * }?}  */(null)
  )
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
endymion1818 profile image
Ben Read

That's great, thanks for the reply. Yeom!

Collapse
 
christiancho profile image
Christian Cho

That post by DHH is just the ramblings of a butthurt developer who is no longer relevant. If you want to prototype something quickly, use whatever tools you need and leave out anything you don't want. But the minute a codebase needs additional developers, TS shines. You shouldn't make blanket statements about how TS isn't productive when numerous enterprise developers are all saying that it's tremendously helpful.

Collapse
 
artxe2 profile image
Yeom suyun

Sure, the wording "TypeScript is not productive" is a bit inappropriate.
However, what I want is for people who are uncomfortable with TypeScript to feel the benefits of type checking by choosing JSDoc instead of using pure JavaScript.
As I wrote this article, considering people who see TypeScript from the same perspective as the developers of turbo8, it seems that this article also has some rambling parts.

Collapse
 
christiancho profile image
Christian Cho

Just be careful with how you frame things. JSDoc is great and it has its place, but it doesn't do everything that TypeScript does and conversely, TypeScript might be a heavy handed approach when trying to rapidly prototype. No tool is going to be the right choice for every problem.

Collapse
 
patroza profile image
Patrick Roza • Edited

For a long time nobody writes plain JS without at least some compilation or transpilation step, especially on the frontend.
Transpilation or comment/type stripping is certainly much faster than (typescript) compilation.
Therefore for a long time most build tools don’t use typescript compiler anymore but just use transpilation with type stripping.
If you want to continue using types, and I think you should, then I believe that is the much better middle ground than verbose and noisy comments. Then the only use for comments remains commenting the code.

Collapse
 
artxe2 profile image
Yeom suyun

As you discussed, the compile time issue of TypeScript has been addressed by tools like esbuild.
However, there are still some issues with TypeScript compilation, such as IDE's Go To Source Definition support.

Collapse
 
patroza profile image
Patrick Roza

Such issues shouldn’t be too hard to address

Collapse
 
rampa2510 profile image
RAM PANDEY

One of the primary reasons I choose TypeScript is for its robust null and error checking capabilities. When I have a function that can either return valid data upon successful execution or an error in case of failure, TypeScript significantly reduces the chances of overlooking error handling when invoking the function. This is crucial for maintaining the reliability and stability of a production system.

When multiple team members are collaborating on a project, ensuring proper error handling becomes paramount. TypeScript's strict typing and type checking help enforce error handling practices across the codebase. While it may require a bit more effort during development, the peace of mind knowing that my code is less prone to runtime errors and is thoroughly handling potential issues is well worth it

Collapse
 
artxe2 profile image
Yeom suyun

It is always wise to prioritize maintenance costs over development costs.
TypeScript's convenience is so great that it might even be cheaper to develop than JavaScript.
I am a strong supporter of TypeScript, and I believe that using the strict option in ts.config as true is essential.
However, I think that TypeScript's excellent JSDoc support has made the compilation step unnecessary overhead.

Collapse
 
akashkava profile image
Akash Kava • Edited

I agree with compilations overhead, due to wrong conversions we have to use babel for code to work correctly. And typescript team doesn’t care to correct it.

But commenting JSDoc is also an overhead, code becomes less cleaner. Comment about logic and comment about the type gets mixed.

The totally wrong thing is the @types namespace as you try to investigate the symbol, it takes you to declaration. You don’t see the source code which is terrible.

I hate @types declaration. Either it should take to typescript source or JavaScript source.

We are using typescript but always with declarations along with map to original source code.

Non accessible source code is against web development ecosystem.

I guess you can ignore some of typescript’s default compilation config to still stay in typescript and make it easy to work with.

  1. No .d.ts without source map.
  2. To fix compilation issues, you can use switches as ts-ignore
  3. Keep JSDoc clean for explanation
  4. Transpiled JS should contain JSDoc with the types.
Collapse
 
artxe2 profile image
Yeom suyun

I completely agree with this comment.
I also felt that JSDoc was dirty when I first saw it.
However, recently I have come to think that comments themselves make code dirty, and that in many cases, no further comments are needed for a function with just the type, function name, and parameter name.
I think that additional comments are needed in JSDoc only in the following cases:

  • When the implementation of the function uses a very complex algorithm and at least some interpretation is required.
  • When the implementation of the function seems to be inefficient, but there is a reason why it should not be refactored.
  • When the naming of the function or parameters is not appropriate.

Even so, JSDoc is a second-best solution that is not the best that current IDEs can offer, and it definitely tends to make code dirty in cases such as type casting.

Collapse
 
barrymichaeldoyle profile image
Barry Michael Doyle

I tried using JSDoc for a contract job I was working on where the person I was working with wasn't used to working with TypeScript. I found it pretty painful TBH without TypeScipt.

I don't want to bash JSDoc, in fact I love using JSDoc for utility functions I build (with TypeScript) purely for the handy extra information I can add for intellisense, but abandoning TypeScript altogether for that job felt counter productive, everything I built took longer to build properly...

Collapse
 
artxe2 profile image
Yeom suyun

If it is not a public library, rewriting the existing codebase from TypeScript to JSDoc may not be desirable.
It is a well-known fact that more than 70% of websites are written in jQuery.
However, if it is a public library, I personally recommend migrating to JSDoc.

Collapse
 
brense profile image
Rense Bakker

The problem with the jsdoc types is duplication of code. If you want to change function arguments, you have to do so in two places. And thus makes the code vulnerable to all problems associated with duplicated code. I can see some merit in the case where you want to use type inference but not strongly type anything, but for strongly typing your own code typescript is still king imho.

I don't understand why people keep bringing up the compilation step... I haven't seen any serious JavaScript project without a compilation step in the past decade... Moreover, it's not like you're recompiling every line of code in your project when you change one thing, the typescript compiler is actually pretty smart about that :) recompilation is pretty much instant.

Collapse
 
artxe2 profile image
Yeom suyun

The problem with the jsdoc types is duplication of code.

Is this a problem that cannot be solved by type checking through tsc --noEmit?

I don't understand why people keep bringing up the compilation step

This is likely because the projects that have issues with TypeScript are open source libraries.

Collapse
 
luisrodriguez profile image
Luis David Rodriuez Valades

Yes I agree that TypeScript could be complex but it is like that for one reason, maybe we haven't invested enough time learning it. I can't imagine working in a project with 50 or 100 developers without TypeScript.

Maybe because we learned JavaScript without strong typing we always will find much more easier to trow TypeScript away the typical "hugrrr this code should work in JavaScript, why I have this error!!!" The same way you can't remove strong typing from C# or Java, if you are chosing TypeScript as your "Language" is because the size of your project require it, how many developers will work in there? A few? Only me?

Would be something horrible to have a huge block of JSDoc above each function you declared, I'm a huge fan of JSDoc but just to describe and provide concise instructions and @examples about how to use a Generic function or utility function that all our team will be using. Something that TypeScript can't say by itself. It is awesome the time that you can save in virtual calls asking other developer how that huge function can be applied to your case just hovering the function and seeing the description provided by JSDoc

Why should you use JSDoc to describe what an argument is supposed to do and be? when the argument name and a TypeScript speak by themselves?

Nonetheless, as it is often said, comments are for cases when the code can't speak by itself and DO NOT trust in comments because very often you update your code but also forget to update the comment.
And this could and will happen if using JSDoc as source of truth is the way to document your code, your function will say one thing and your JSDoc another.

Collapse
 
luisrodriguez profile image
Luis David Rodriuez Valades

As @mellis481 says "Freedom from types" = "freedom from seatbelts".
When cars didn't have seatbelts people needed a period of time to start using them, my grandparent didn't like to use seatbelts when going in my car, just because he knew and get used to a different way to travel.

Collapse
 
artxe2 profile image
Yeom suyun

If @param arg1 and other similar annotations seem to clutter up JSDoc, you can solve this by writing important information such as @example above them.
If you use JSDoc in JS with TypeScript settings, a type error will occur when the comment and the source do not match.
I realized this after looking at various comments, but it seems that everyone is talking about their experience using JSDoc in TypeScript, so their understanding is not aligned.

Collapse
 
pengeszikra profile image
Peter Vivo • Edited

I wouldn't think that the problem that can be implemented with Typescript can be solved with JSDoc. Specifically, how a function declared with user-defined state and action union types, returns a collection of actions equipped with dispatch, which assigns the appropriate types to the appropriate labels. This provides a type-safe useReducer solution for programmers.

import { FC } from "react";
import { useStateFactory } from "react-state-factory";
import { reducer, initialState, labels } from "@foo/reducer";


export const FooComponent:FC = () => {
  const [state, put] = useStateFactory(reducer, initialState, labels);

  // state and put types defined by user with initialState and labels state
  // in this example one of put actions is put.INSERT_CONTENT(content: number)
  // VSCode or any typescript friendly editor help to list all aviable commands, 
  // with proper parameter(s) type.

  return (
    <section>
      <button onClick={() => put.INSERT_CONTENT(Date.now())}></button>
      <pre>{JSON.stringify(state, null, 2)</pre>
   </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

About this npm module: react-state-factory

Thus, working with JSDoc in this way may entail too much additional effort and make the codebase much more difficult to maintain.

Collapse
 
bluebaroncoder profile image
James

No. You don't just get to call people mean names like zealots just so you don't have to learn something essential. Type safe languages prevent countless bugs and save thousands of hours on the life of projects.

Collapse
 
artxe2 profile image
Yeom suyun

I am not opposed to type-safe languages.
However, I question whether the term "zealots" is too strong for people who express their support for these PRs with emojis.

Collapse
 
bluebaroncoder profile image
James

From one ad hominem attack to the next. Reality isn't based on your feelings of what someone else is doing wrong or how they're living their lives.

Thread Thread
 
artxe2 profile image
Yeom suyun

Definitely, it seems like it would be good to separate their actions and my actions.
That's good advice.

Thread Thread
 
bluebaroncoder profile image
James

That's not at all what I said. You are not behaving appropriately. You are saying that your argument is based on how you feel about a person. That is the wrong way to make an argument. That is simple Aristotle, Plato, Socrates. You don't abuse your opponent to make your argument. If you do that, your argument is inappropriate.
Never, ever would I want someone to type in my name, especially by an employer or family or friend, and have them see what you wrote here and how you abused commenters and made fallacious arguments about something you don't understand. This is embarrassing.

Thread Thread
 
artxe2 profile image
Yeom suyun

I think our communication is not going well because I am not a native English speaker.
Here is what I understand:

  • You pointed out my aggressive language in my post, and I asked if it was inappropriate by attaching a PR that removes Turbo 8 entirely.
  • You then said something about your feelings and reality, and I replied that I need to behave properly regardless of someone's actions.
  • However, you said that I did not understand your words at all, and I am abusing someone to make my argument, which is wrong. Is my understanding correct?

how you abused commenters and made fallacious arguments about something you don't understand.

What process led you to this conclusion?
You seem to be feeling very uncomfortable, but this seems a bit out of place in the flow of the conversation as I understand it.

Collapse
 
efpage profile image
Eckehard • Edited

We should not forget, that missing types are not the only thing that distinguishes Javscript from a compiled language. If you write some code in C++ or Pascal, all parts of the code are visited during compilation. So, the compiler is able to find errors that are hidden somewhere in the code, even if this code is not actually executed. Same is true for tree shaking, that can be done by the Linker automatically.

In Javascript, things are different. Many errors stay hidden somewhere in the code, until it is finally executed. So, the ability to do code checks is limited anyway. Typescript may help a bit, but the result is still different from what we have been used to.

So for me the effort using typescript does not pay back most of the time. Adding some more type checks to the code helps, to keep things running.

Collapse
 
artxe2 profile image
Yeom suyun • Edited

I believe that there are many people, including you, who believe that pure JavaScript is more productive than TypeScript.
TypeScript's type checking may be seen as unnecessary if you have a well-written testing process.
However, JSDoc has the advantage of providing a go to source definition feature for TypeScript users.
This allows users to verify that library code is safe and is a great help for debugging and contributing if a bug occurs.

Collapse
 
efpage profile image
Eckehard

No, that´s not the point. I hate to know that my code is full of hidden errors and can break anytime. It´s more a feeling that Typescript does not solve the problem in the same way, a full featured compiled language did. But this is not typescripts fault, it is more a price you pay, if you use an interpreted language.

Collapse
 
andrzej_kowal profile image
Andrzej Kowal

I definitely fully agree with @mellis481. Article sounds as a joke. TypeScript is the one of the best things which happen with JavaScript world for last 10 years. JSdoc looks like a weird workaround, while typescript gives many great mechanisms and utilities to supply codebase with advanced types including generics. Not that much time ago I heard about Bun. It’s a promising game changer that hopefully will decrease amount of “setup” work of typescript and will greatly speed up (hopefully). And even with all the cons of TypeScript - it’s one of the best (and proven to be the best) tools which everyone should use.

Collapse
 
artxe2 profile image
Yeom suyun

I somewhat disagree with the arguments about Bun, but I definitely agree with the arguments about TypeScript.
However, JSDoc is fully supported by TypeScript for type checking, and there are definitely significant benefits to adopting it.