Remix.run Logo
culi a day ago

Well I'd love to hear some concrete examples if you have any on hand! I was of the same opinion as you until I refactored a project of mine to use JSDoc.

Since any TypeScript type can be expressed in JSDoc, I imagine you're mostly thinking of generics. At least that was my main sticking point. JSDoc does actually have generic slots with the @template tag. Actually using them in practice is a little unintuitive but involves typing the return type. E.g. for a function it'd look like this:

  /** @type {ReturnType<typeof useState<Book[]>>} */
  const [books, setBooks] = useState();
g947o a day ago | parent | next [-]

Last time I checked, there isn't a way to extend types (https://www.typescriptlang.org/docs/handbook/2/objects.html#...) that works exactly like in TypeScript.

culi a day ago | parent | next [-]

JSDoc actually has the @extends tag

  /**
   * @typedef {object} Dog @extends Animal
   * @property {string} childProp
   */
But I don't really use that feature in TypeScript. Instead I rely on `&`. This works in exactly the same way in JSDoc.

Also if you're curious about the equivalent of `extends` in generic slots, here's an example I have from a different project

  /**
   * @template {Record<string, unknown>} [T=Record<string, unknown>]
   * @typedef {{
   *   children?: NewickNode<T>[];
   *   name?: string;
   *   length?: number;
   *   data?: T;
   * }} NewickNode
   */
The generic slot here, T, is "extended" by Record<string, unknown>. The equivalent in TypeScript would look like

  type NewickNode<T extends Record<string, unknown> = Record<string, unknown>> = {
    children?: NewickNode<T>[];
    name?: string;
    length?: number;
    data?: T;
  };
llimllib a day ago | parent | next [-]

the equivalent in typescript would be "export type" not just "type", since as I pointed out that type is exported without you being able to control it

KPGv2 a day ago | parent | prev [-]

I don't understand why someone would opt for "write Typescript, but add a bunch of extra characters, make the syntax clunkier, and throw away all the benefits of compile-time errors because we'd rather have runtime errors" in order to save, what, a microsecond stripping typescript out of TS files?

Everyone's complaining about "the build step" but the build step is just an eye blink of stripping out some things that match a regex.

culi a day ago | parent [-]

> throw away all the benefits of compile-time errors because we'd rather have runtime errors

This is inaccurate on multiple counts. First of all, you can still run tsc with JSDoc if you want a hard error and you can still use strict mode with JSDoc. Your tsconfig file governs JSDoc-typed code just the same as it governs .ts-typed code. In both cases you can also ignore the red squigglies (the exact same red squigglies) and end up with runtime errors.

Nobody is advocating for reduced type safety or increased runtime errors.

I also think there are many valid reasons to loathe a build step (like not dealing with the headache that is the discrepency between the way the TS compiler deals with import paths vs js runtimes).

All that being said, I'm not really trying to convince anyone to stop using TypeScript. I'm simply pointing out that using JSDoc is using TypeScript. It's the same language service.

a day ago | parent | prev | next [-]
[deleted]
auxiliarymoose a day ago | parent | prev [-]

You can use `&` operator to combine types. Works for adding fields, making branded types, etc.

g947o a day ago | parent [-]

which is not the same as "extends".

spoiler a day ago | parent [-]

There's also a slight performance caveat when it comes to interfaces vs intersections (&) to keep in mind: https://github.com/microsoft/Typescript/wiki/Performance#pre...

mirekrusin a day ago | parent | prev | next [-]

Things like type star imports, destructured imports or satisfies operator don't work in jsdoc.

All non erasable constructs won't work as well of course but playing devil's advocate you could explicitly state that you're interested in erasable constructs only because ie. 1) that's what typescript should be doing from day 1 and/or 2) it seem to be the future with ie. nodejs adopting built in type erasure support.

g947o a day ago | parent | prev | next [-]

Support for generics is limited in JSDoc compared to TypeScript, especially when arrow function is involved. Things that work fine in TypeScript trigger errors even though they are syntactically the same.

culi a day ago | parent [-]

Because AirBnB's ESLint config has been burned into my brain I almost only use arrow functions. I also use generics extremely often. It's definitely a little more clunky but I haven't run into anything you can do in TypeScript that you can't do in JSDoc.

JSDoc also allows you to type stuff in-line. For example I often have to type an empty array like so:

  const [books, setBooks] = useState(/** @type {Book[]} */([]));
If you have a tangible example of a problem you've run into, I'd love to walk through it.
g947o a day ago | parent [-]

JavaScript example:

https://www.typescriptlang.org/play/?filetype=js#code/PTAEAE...

Almost equivalent typescript code:

https://www.typescriptlang.org/play/?#code/C4TwDgpgBA6glsAFg...

(I had to make it a little bit different from the JS code to make it compile)

(Well, this is not exactly about arrow function I guess. I remembered that part wrong.)

Note that I cannot make the type check in JS code to pass. Whatever I do, there is always a error. Meanwhile, it does not take much to TS code to work.

culi a day ago | parent [-]

Try this out. I think it's a much more faithful representation of your TypeScript example too. JSDoc just wants you to be explicit about the default for generic slots (which is `unknown` in TypeScript).

https://www.typescriptlang.org/play/?filetype=js#code/PTAEAE...

Hover over the variables and you should see that the type inference is working just the same as in your TypeScript example

g947o a day ago | parent [-]

Ah I see. Looks like you avoided using the "@callback" tag but instead used the "@typedef" tag directly. Thanks!

I do think it illustrates a problem with TypeScript's support for JSDoc though. You see, I started with the code in JS and could not make it work, after which I translated it to TS. In JS/JSdoc, "@callback" is the "idiomatic" way of defining a function callback type with JSDoc. (It also makes it easier to add documentation for each parameter if necessary.) And indeed, @callback works the most of the time, except in such cases where these JSDoc tags don't work nicely together, and these alternatives become necessary.

culi a day ago | parent [-]

This is absolutely a fair point. JSDoc predates TypeScript by over a decade. It's not until more recently that the syntaxes have started to converge.

My brain definitely works in TypeScript so I tend to translate from there. I definitely consider myself more familiar with TypeScript than with JSDoc, but sometimes (e.g. here) that's a benefit not a weakness

efortis a day ago | parent | prev | next [-]

and types can be written in .d.ts file(s), which can be used in jsdoc out of the box (no import needed)

mirekrusin 20 hours ago | parent | next [-]

that's not "jsdoc is typescript" anymore because you're falling back to something else - type definition files where you do have access wider typescript functionality.

also not a full solution - for .d.ts types to be available globally without explicit import the .d.ts file itself cannot use any imports/exports. this means you can't reuse types from other places to construct your types. you can workaround this by explicitly importing .d.ts in jsconfig/tsconfig but you're still left with other issues.

those types do actually become globally visible everywhere polluting global namespace which is bad in itself

there are no guarantees about them being in sync with actual code, which violates the whole point of using type safety.

they don't solve cases where you need typescript inlined functionality locally in your code or to perform assertion with satisfies operator etc.

g947o a day ago | parent | prev [-]

You could, but in a large codebase with multiple contributors, it easily becomes messy and confusing.

afavour a day ago | parent | prev [-]

ReturnType is TypeScript, no? You’re using JSDoc to express but it’s a TypeScript type.

culi a day ago | parent | next [-]

As I stated in the article, JSDoc is TypeScript :P

TypeScript utility types are available in JSDoc. You can pretty much copy-paste any typescript Type/Interface into JSDoc

Mogzol a day ago | parent | prev [-]

Isn't that the whole point of the article? For all intents and purposes, JSDoc IS TypeScript