Top
Best
New

Posted by culi 1 day ago

JSDoc is TypeScript(culi.bearblog.dev)
201 points | 252 comments
Waterluvian 1 day ago|
A few things I've come to personally believe after spending years developing web and robotics software in Python/JavaScript then spending years having to maintain while constantly adding new features and dealing with company pivots:

- The types exist whether you write them down or not.

- If they're not written down, they're written down in your head.

- Your head is very volatile and hard for others to access.

- Typing is an incredibly good form of documentation.

- JSDoc and TypeScript are standards/formats for typing. Like any tools, they both have advantages and disadvantages. Neither is objectively better than the other.

- Make informed decisions on how you'll describe your types, and then be consistent and unsurprising.

- A type checker is the computer saying, "okay then, prove it" about your program's type validity.

- Not every program benefits from the same amount of "prove it."

- Too much can be as bad as too little. You're wasting resources proving throwaway code.

- I like languages that let you decide how much you need to "prove it."

Culonavirus 18 hours ago||
> Your head is very volatile and hard for others to access.

One of the lessons you learn while doing this job is that "others" includes "yourself in the future".

(Of course people will tell you this way before you find out yourself, but what do they know...)

have_faith 14 hours ago||
"who wrote this garbage?... oh yeah that was me"
pavel_lishin 10 hours ago||
Nothing puts you in your place like a `git blame`.
john01dav 23 hours ago|||
> - I like languages that let you decide how much you need to "prove it."

Rust is known for being very "prove it," as you put it, but I think that it is not, and it exposes a weakness in your perspective here. In particular, Rust lets you be lax about types (Any) or other proved constraints (borrow checker bypass by unsafe, Arc, or cloning), but it forces you to decide how the unproven constraints are handled (ranging from undefined behavior to doing what you probably want with performance trade-offs). A langauge that simply lets you not prove it still must choose one of these approaches to run, but you will be less aware of what is chosen and unable to pick the right one for your use case. Writing something with, for example, Arc, .clone(), or Any is almost as easy as writing it in something like Python at the start (just arbitrarily pick one approach and go with it), but you get the aforementioned advantages and it scales better (the reader can instantly see (instead of dredging through the code to try to figure it out) "oh, this could be any type" or "oh, this is taken by ownership, so no spooky action at a distance is likely").

MrJohz 13 hours ago|||
In practice, though, writing stuff with `Arc` or `.clone()` or `Any` is not as easy as it would be in Python because you've got to write a bunch of extra boilerplate. It's much easier to have local `i64` values that you can pass around as `Copy`. So if all you need are local i64s, then you'll take the easier option and do that.

The same is true at multiple levels. `.clone()` is relatively easy to use, although once you learn the basic rules for referencing, that also becomes easier. `Arc` solves a specific problem you run into at a certain point sharing data between threads, but if you're not sharing data between threads (and most of the time you're not), it's just boilerplate and confusing, so you might avoid it and at worst use `Rc`. `Any` is rarely an obvious choice for most contexts, you really are going to only use it when you need it.

The result is that for most simple cases, the precise and "proven" option is typically the easiest to go for. When you deal with more complicated things, the more complicated tools are available to you. That seems exactly what the previous poster described, where you can decide yourself how much you need to prove a given thing.

throawayonthe 16 hours ago|||
is that not exactly "deciding how much to prove it?"
culi 1 day ago|||
> JSDoc and TypeScript are standards/formats for typing. Like any tools, they both have advantages and disadvantages. Neither is objectively better than the other.

Agreed. Just to clarify, my intentions with this post weren't to advocate for one over the other. Just to point out that they are the same thing. They are both TypeScript.

mattmanser 15 hours ago|||
I don't know how anyone can agree with this, because it pivots on this outrageous claim:

"Like any tools, they both have advantages and disadvantages"

You cannot make any argument based on such a position. Putting aside anyone's views on TS or JSDoc, tooling is of extremely variable quality, and lots of tools ARE objectively much worse than other tools.

It we can't point at tools and say this one is better than that one, we might as well give up.

I always remember a scene in Will & Grace where Will is trying to get his boss to say one thing is better than the other. He's brought a lovingly hand-crafted sandwich made with amazing, bread, fillings, etc. and a store bought sandwich made with cheap bread/fillings. He asks his boss to try both and say which one he likes more.

His boss says something like the store bought one reminds him of his grandma's sandwiches, so invokes nostalgia, and still can't make a decision.

Don't be that boss.

rpsw 14 hours ago||
I don't understand how this an outrageous claim. You might say a luxury sports car is objectively better than a cheap minivan, but that is no good to someone who needs to transport around a family.

Or is you problem with the word any instead of many?

KPGv2 21 hours ago|||
> They are both TypeScript.

I take issue with this position because this seems to imply "PureScript and JavaScript are both JavaScript" is a true statement merely because one of them turns into the other with tooling.

nosianu 16 hours ago|||
TypeScript is indeed Javascript, all you have to do is remove the type annotations. They are not code.

TS does have some minor things like enums that need to be transformed and are actual code, but those are very few, and leftovers from early days of TS, and the TS authors regret having implemented them. For many years now the TS philosophy has been that the CODE part of TS is 100% ECMAscript, and only annotations, which are not code, are added.

The initial Babel transpiler for TS => JS, and still the most part of the current one, simply removes annotations.

It is recommended not to use the few parts that are actual code and not standard JS. They are certainly not needed any more since ES6.

People may get confused because the type syntax itself is almost like a programming language, with conditions and all. But none of that ends up as code, it's not used at runtime.

One of the IMHO worst design decisions of TS was to bundle type checking and transpiling into one tool. That caused sooo many misunderstandings and confusion.

egeozcan 20 hours ago|||
I think the point here is that all the TS tooling works with JSDoc without any friction. As long as you don't look into the file, from the tooling perspective, a .ts file and a .js file with proper JSDoc annotations are practically the same.
aatd86 19 hours ago||
Except the js files can work in the browser as is.. not the ts one (fortunately I might add, I find ts syntax very loaded at times) Either one is superseding the other one, or they are simply distant cousins, but this is not interchangeable.

TS from JSDoc requires a generative pass to. This is a (expected) level of indirection. (unless some tooling does it automatically)

christophilus 11 hours ago|||
Yeah. I learned this lesson 25 years ago in university. SQL, Lisp, and whatever scripting language I was learning at the time were always much harder to reason about vs statically typed languages— particularly in team projects. Over the years, I’ve gotten comfortable with SQL, but I always prefer static typing when I can get it.
dominicrose 10 hours ago|||
> The types exist whether you write them down or not

That's easy to say when we're talking about primitive arguments in private functions, or primitive local variables, but let's not ignore the fact that it takes much more work to write a C# program than a Ruby program for instance.

We can see that by looking at a vanillajs library's typescript typings that were created after the js library when typescript didn't exist. The types are insanely complex and if you get one type wrong you can break compilation of some library user's program (its happened to me).

That being said I'm aware that dynamic programming languages are a "use at your own risk" type of language.

dzonga 7 hours ago||
are dynamic languages really a "use at your own risk" ? or it's about changing frame of mind ?

my take is if you treat your program as a series of data flows - then use primitives such as maps | arrays - then you don't need as much typing or typing at all. a map doesn't need to take a shape or a Person | Manager - either the keys exist or they don't and almost every language has guards to ensure you can safely navigate existence of keys.

but then again my realm is mostly around - web | data systems - if I was dealing with OS level systems and needed to make sure i have i64 ints then yeah typing would be crucial.

jama211 21 hours ago|||
And if your boss is the type to pivot way too often and want it done yesterday, then they don’t deserve code that is prove in the right places so you just get an LLM to add typing to everything after the fact…
jve 18 hours ago||
> then they don’t deserve code that is prove

I don't get it. Types are a way to write code. Nothing to do with how fast/much the code changes.

scotty79 15 hours ago||
Typed code is significantly more cumbersome to change (because there's more code to change). At least until you reach a certain size of your spaghetti bowl. Then both typed and untyped are cumbersome and people endlessly discuss which one is more.
skydhash 14 hours ago||
I don’t think so. With a good editor, you change the type and run the compiler which will warn you with all the locations you’ll need to edit. Then you can quickly navigate to those locations.
embedding-shape 12 hours ago|||
> which will warn you with all the locations you’ll need to edit

This is true, but with a program built with a dynamic language, taking advantage of the fact that it's written in a dynamic language, doesn't need to make those changes at all.

I'm fan of static typing in many situations, but it's hard to deny it doesn't lead to more changes as you need to properly propagate changes.

ARandumGuy 10 hours ago||
I don't find that dynamic typing reduces the number of places I need to update stuff. It just changes when the error occurs.

If I change the shape of some data (such as renaming object properties), I'll need to update all the code that used that data, regardless of the type system. Static typing just ensures that I catch those cases at compile time, not runtime.

embedding-shape 9 hours ago||
Just one simple and contrived example I could come up with the five minutes I had available:

JavaScript:

  // rename host -> hostname, update ONE place
  function connect(opts) {
    const host = opts.hostname ?? opts.host; // compat shim
    return `tcp://${host}:${opts.port}`;
  }

  // old call sites keep working
  connect({ host: "db", port: 5432 });
  connect({ host: "cache", port: 6379 });

  // new call sites also work
  connect({ hostname: "db", port: 5432 });
TypeScript:

  // same compat goal, but types force propagation unless you widen them

  type Opts = { port: number } & ({ host: string } | { hostname: string });

  function connect(opts: Opts) {
    const host = "hostname" in opts ? opts.hostname : opts.host;
    return `tcp://${host}:${opts.port}`;
  }

  // If instead you "just rename" the type to {hostname; port},
  // EVERY call site using {host; port} becomes a compile error.
Again, this is just a simple example. But multiply 100x + way messier codebases where everything are static types and intrinsically linked with each other, and every change becomes "change -> compile and see next spot to change -> change" until you've worked through 10s of files, instead of just changing it in one place.

Personally, I prefer to spend the extra time I get from dynamic languages to write proper unit tests that can actually ensure the absence of specific logic bugs, rather than further ossifying the architecture with static types while changes are still ongoing.

ARandumGuy 9 hours ago||
In your Typescript example, the solution would be to use your IDE to refactor hosts to hostnames, a process that takes like 2 seconds. You might have problems if the change exists at a service boundry, but in that case I'd just put the transformation at the service boundry, and keep everything the same internally.

> Personally, I prefer to spend the extra time I get from dynamic languages to write proper unit tests that can actually ensure the absence of specific logic bugs, rather than further ossifying the architecture with static types while changes are still ongoing.

I'd argue static typing makes this much easier, because I know any input types (or output types from other components) will be enforced by the type system. So I don't need to bother writing tests for "what if this parameter isn't set" or "what if this function returns something unexpected". The type system handles all of that, which eliminated a lot of tedious boilerplate tests.

embedding-shape 8 hours ago||
> In your Typescript example, the solution would be to use your IDE to refactor hosts to hostnames

Yeah, sure, and with LLMs you can do this, and you can do that. But if we're talking about languages and their features, relying on IDE features feels slightly off-topic.

But regardless, changes is changes, no matter if you, your IDE or your LLM made them. So even if your IDE makes the changes, it seems at least you can now agree that there are more changes needed, it's just that with TypeScript you have a editor who can help you refactor, and with JavaScript you haven't yet found an editor that can do so.

> So I don't need to bother writing tests for "what if this parameter isn't set" or "what if this function returns something unexpected".

Yeah, those unit tests does nothing, and people who write in dynamic languages don't write tests like that either. You test actual logic, in unit tests, and you rely on the signals that gives you.

In fact, I could bet you that if you and me both sat down and wrote the exact same application, one in JS and one in TS, we'd end up with more or less the same amount of unit tests, yet the JS codebase will be a lot more flexible once product requirements start to change.

But again, YMMV and all that, it's a highly personal preference. I don't think there is a ground truth here, different minds seem to prefer different things. I mostly work in environments where the requirements can change from day to day, and being able to adopt to those without introducing new issues is the most important thing for me, so with JS I stay.

ARandumGuy 8 hours ago||
> Yeah, sure, and with LLMs you can do this, and you can do that. But if we're talking about languages and their features, relying on IDE features feels slightly off-topic.

Refacoring tools (such as renaming properties) have been supported by IDEs for decades. And in Typescript specifically, the language is designed with these tools in mind, which are developed and distributed directly by the Typescript team. For all intents and purposes, IDE integration using the Typescript language server is a feature of Typescript.

And if somehow these tools don't work, the compiler will catch it immediately! This means I can refactor with confidence, knowing any type issues will be caught automatically.

It seems like you're vastly overestimating the time and effort it takes to change types in Typescript. In my experience it's something that takes basically no time and effort, and has never caused me any issues or headaches.

embedding-shape 3 hours ago||
You're changing the point though, you claimed "I don't find that dynamic typing reduces the number of places I need to update stuff", which just because your editor makes the change, doesn't suddenly make the change not show up in the diff for you and others. A change is a change, regardless of how it's made, unless you don't keep your code in a SCM that is, which I'm assuming most of us do.

I'm not saying it's hard to change types in TypeScript, I understand that your IDE is connected with the language and they're used together, but again my argument was that having types leads to having to change things in more places. Which is true too, and it's a good thing, it's on purpose, and the tools you use help with that, so yay for that! But it's still more changes, and at least for me it's important to be honest about the tradeoffs our choices leads us to.

As I mentioned earlier, I'm fan of static typing in many situations, just not all of them. And I'm not blind to the negatives they can bring too, I guess I just see more nuance in the static typing vs dynamic debate.

skydhash 3 hours ago||
Regardless of how dynamic typing is, the contract being enforced stays the same. It's just less enforced by tools. So with dynamic typing, you will have to change the same amount of code if you want your code to stay correct. The only variation in code changes comes from the annotation for the type system.
scotty79 1 hour ago||
> So with dynamic typing, you will have to change the same amount of code if you want your code to stay correct.

No, because if a piece of data is pushed through multiple layers you can just change its type at the source and the destination and not in all the layers the data is pushed through. And you can still be correct.

Imagine you have a thing called target which is a description of some endpoint. You can start with just a string, but at one point decide that instead of string you'd prefer object of a class. In dynamic language you just change the place where it originates and the place where it's used. You don't need to change any spot in 3 layers that just forearded target because they were never forced assumed it's a string.

You can achieve that in staticly typed language if you never use primitive types in your parametrs and return types or if you heavily use generics on everything, but it's not how most people write code.

Tools can help you with the changes, but such refactors aren't usually available in free tools. At least they weren't before LLMs. So the best they could do for most people was to take them on a journey through 3 layers to have them make manual change from string to Target at every spot.

skydhash 39 minutes ago||
In some type systems, you don't need to change it either. Errors will happens only if you've written code that assumes that `target` is a string. If I write a function with the following signature:

  fn split(str: string) => string[]
And you called it with

const first_el = split(target)[0]

If you decide `target` to suddenly be an object, then the code you wrote is incorrect, because semantically, `split` only makes sense when the argument is a string. Which is why, even with dynamic typing, you'll see that the documentation of a function will state what kind of parameters it expects.

If you a call chain like `fn1 | fn2 | fn3 | fn4 | split`, Then yeah you need to ensure what reaches `split` is in fact a string. If you're against updating fn[1-4]'s signature, let's say that it's harder to find which function needs to change in a dynamic typing systems.

Dynamic typing is useful for small programs and scripts because they are easy to iterate upon. But for longer programs, I'll take static typing anytime.

scotty79 12 hours ago|||
Yeah, tooling for strongly typed languages is way better than 20 years ago. They are getting very being usable.
scotty79 18 hours ago|||
> I like languages that let you decide how much you need to "prove it."

I love that too. Are there any other languages than TS that have this as a core design feature?

jve 16 hours ago||
As I understand that is the core design feature of any strongly typed language.
matt_kantor 9 hours ago|||
I believe they're talking about gradual typing[0].

[0]: https://en.wikipedia.org/wiki/Gradual_typing

scotty79 3 hours ago|||
Not really. Strongly typed languages don't usually support not having any types in your code (or any amount of gradual typing). At least not with any convenient syntax. There's usually some one clumsy "dynamic" type that doesn't interact with the rest of the system well and you'd be crazy to start writing your code using only this dynamic type.

I can't just write C++ like:

  any a = 1;
  a = "Hi!";
I also can't tell JS this shouldn't be allowed. But I can tell this to TS, at any stage of evolution of my program.
user3939382 23 hours ago||
I'll take a more definite position. Build pipelines are the devil. Every square inch of surface complexity you add to whatever you're doing, you will pay for it. If there's a way to get the benefits without the build step, that's your choice. You may need runtime type enforcement, so that's not JSDoc, and you're doing that with... JS so that's already suspicious but the use case def exists. JS, the DOM, HTTP, this whole stack is garbage at this point so arguing about the type system is a little idk hard to care about personally. I have a solution that lets you discard most of the web stack, that's more interesting to me.
prisenco 1 day ago||
I'm a fan of anything that allows me to build with javascript that doesn't require a build step.

Modern HTML/CSS with Web Components and JSDoc is underrated. Not for everyone but should be more in the running for a modern frontend stack than it is.

bobbylarrybobby 1 day ago||
On the one hand I can see the appeal of not having a build step. On the other, given how many different parts of the web dev pipeline require one, it seems very tricky to get all of your dependencies to be build-step-free. And with things like HMR the cost of a build step is much ameliorated.
prisenco 1 day ago||
I haven't run into any steps that require one, there's always alternatives.

Do you have anything specific in mind?

webstrand 1 day ago||
Anything that uses JSX syntax, for instance.

Any kind of downleveling, though that's less important these days most users only need polyfills, new syntax features like `using` are not widely used.

Minification, and bundling for web is still somewhat necessary. ESM is still tricky to use without assistance.

None of these are necessary. But if you use any of them you've already committed to having a build step, so adding in a typescript-erasure step isn't much extra work.

brazukadev 1 day ago|||
If there is one thing I don't miss using WebComponents is JSX. lit-html is much, much better.
claytongulick 1 day ago||
It's such a lovely and simple stack.

No Lit Element or Lit or whatever it's branded now, no framework just vanilla web components, lit-html in a render() method, class properties for reactivity, JSDoc for opt-in typing, using it where it makes sense but not junking up the code base where it's not needed...

No build step, no bundles, most things stay in light dom, so just normal CSS, no source maps, transpiling or wasted hours with framework version churn...

Such a wonderful and relaxing way to do modern web development.

prisenco 23 hours ago||
I love it. I've had a hard time convincing clients it's the best way to go but any side projects recently and going forward will always start with this frontend stack and no more until fully necessary.
brazukadev 22 hours ago||
This discussion made me happy to see more people enjoying the stack available in the browser. I think over time, what devs enjoy using is what becomes mainstream, React was the same fresh breeze in the past.
Calazon 12 hours ago|||
I recently used Preact and HTM for a small side project, for the JSX-like syntax without a build step.
odie5533 22 hours ago|||
I have not written a line of JavaScript that got shipped as-is in probably a decade. It always goes through Vite or Webpack. So the benefit of JS without a build step is of no benefit to me.
prisenco 22 hours ago||
Dare to dream and be bold.

Seriously, start a project and use only the standards. You'll be surprised how good the experience can be.

junon 1 day ago|||
Webcomponents are a pain in the ass to make, though. That is, sufficiently complex ones. I wish there was an easier way.
eric-p7 23 hours ago|||
I've built Solarite, a library that's made vanilla web components a lot more productive IMHO. It allows minimal DOM updates when the data changes. And other nice features like nested styles and passing constructor arguments to sub-components via attributes.

https://github.com/Vorticode/solarite

jeswin 23 hours ago||||
It's ok now, at least for me. There are still challenges around theming and styling because of styling boundaries (which makes Web Components powerful, but still). A part of it is about tooling, which can be easier to improve.

Try my tiny web components lib if you want to keep JSX but not the rest of React: https://github.com/webjsx/magic-loop

prisenco 1 day ago||||
They could have better ergonomics and I hope a successor that does comes out but they're really not that bad.
brazukadev 1 day ago||
web components need 2 things to be great without external libraries (like lit-html):

- signals, which is currently Stage 1 https://github.com/tc39/proposal-signals

- And this proposal: https://github.com/WICG/webcomponents/issues/1069 which is basically lit-html in the browser

junon 7 hours ago||
It's a shame Surplus (Adam Haile's, not my succession of it) isn't cited nor is he mentioned, given that at least two of the listed frameworks were heavily and directly inspired by his work. S.js is probably one of the most incredible JavaScript libraries I've used that should be the reference for a signal API, in my opinion.
gedy 11 hours ago|||
Svelte has a pretty nice support for this via https://svelte.dev/docs/svelte/custom-elements

It's not a no-build option though.

auxiliarymoose 1 day ago|||
Agreed on native HTML+CSS+JSDoc. An advantage in my use-cases is that built-in browser dev tools become fluid to use. View a network request, click to the initiator directly in your source code, add breakpoints and step without getting thrown into library internals, edit code and data in memory to verify assumptions & fixes, etc.

Especially helpful as applications become larger and a debugger becomes necessary to efficiently track down and fix problems.

mmcnl 4 hours ago|||
You don't need a build step anymore with TypeScript since Node 24.
prisenco 4 hours ago||
I'm referring to client-side javascript.
fergie 11 hours ago|||
> Modern HTML/CSS with Web Components and JSDoc is underrated.

I've been a front end developer for 25 years. This is also my opinion.

mb2100 16 hours ago|||
This. Or use ts-blank-space if you prefer TypeScript over JSDoc. That's what we do in https://mastrojs.github.io
pjmlp 16 hours ago|||
I was already doing that in 2010, with the JSDoc tooling in Eclipse and Netbeans back then.

However I don't get to dictate fashion in developer stacks.

winrid 22 hours ago|||
TS is worth the build step.
prisenco 22 hours ago||
JSDoc is TypeScript.
KPGv2 21 hours ago||
It is TypeScript in the same way my rear end is the Grand Canyon: they are somewhat isomorphic but one is much less pleasant to look at.
mmcromp 23 hours ago|||
Why? The half a second for the HMR is taking up too much your day?
prisenco 23 hours ago|||
No, because layers of abstraction come at a cost and we have created a temple to the clouds piled with abstractions. Any option to simplify processes and remove abstractions should be taken or at least strongly considered.

Code written for a web browser 30 years ago will still run in a web browser today. But what guarantee does a build step have that the toolchain will still even exist 30 years from now?

And because modern HTML/CSS is powerful and improving at a rapid clip. I don't want to be stuck on non-standard frameworks when the rest of the world moves on to better and better standards.

johannes1234321 11 hours ago||
> Code written for a web browser 30 years ago will still run in a web browser today.

Will it? - My browser doesn't have document.layers (Netscape) It seems to still have document.all (MSIE), but not sure it's 100% compatible to all the shenanigans from the pre-DOM times as it's now mapped to DOM elements.

prisenco 9 hours ago||
The Space Jam website from 1996 still renders perfectly almost 30 years later.

https://www.spacejam.com/1996/

Those (document.layers and document.all) were both vendor-specific, neither were part of the w3c. I don't recommend ever writing vendor-specific code.

The w3c and standards have generally won so it's easier than ever to write to the standard.

xigoi 10 hours ago|||
Having all your code go through a multi-step process that spits out 30 different files makes it impossible to know what’s really happening, which I’m uncomfortable with.
kellengreen 8 hours ago||
Came here to write this exact sentiment. Not everything needs a massive build pipeline.
etoxin 1 day ago||
So, some history. When SPA's started to boom on the web JSDoc was a life saver for typing. Application state was getting more complex. We needed more guard rails.

Then Google Closure Compiler came along which added type safety via JSDOC and TS came along with (TS)JSDoc support and it's own TS syntax.

The community chose native TS and Google Closure compiler slipped away into the background.

So (TS)JSDoc support is a relic from when Microsoft was trying to get market share from Google.

Today in 2025, TS offers so much more than the (TS)JSDoc implementation. Generics, Enums, Utility types, Type Testing in Vitest, typeguards, plus other stuff.

Today I use TS. I also use plain JSDoc for documentation. e.g. @link and @see for docs. Or @deprecated when I'm flagging a method to be removed. @example for a quick look up of how to use a component.

TS and plain JSDoc are both important together. But (TS)JSDoc alone, is a relic of the past.

culi 1 day ago||
> Today in 2025, TS offers so much more than the (TS)JSDoc implementation. Generics, Enums, Utility types, Type Testing in Vitest, typeguards, plus other stuff.

This was my main impetus for writing this article. Modern JSDoc uses the TypeScript language service. You can use generics, utility types, typeguards (including the `is` keyword), regex parsing, etc all with just JSDoc.

I used these features extensively (especially generics) in a personal project and managed to do it all in JSDoc.

apatheticonion 21 hours ago|||
JSDoc is missing a lot of basic capabilities. For example a TypeDef is automatically exported which can cause collisions and forces you to repeat or inline types.

Types for classes are poor and often you'll find yourself creating a `.d.ts` file or `.ts` file to export non trivial types - however the target file doesn't know how to consume them.

culi 20 hours ago||
typedefs are indeed automatically exported but that doesn't mean collisions can happen. You would still have to explicitly import a type

Regardless, I hardly consider that a "missing basic capability"

I don't know what you mean about types for classes being "poor". Types for classes work exactly the same way

apatheticonion 20 hours ago||
You cannot replicate `import type { x } from './foo'` without also re-exporting that import - which causes collisions.

The alternative is to do an inline `const foo = /** @type {import('./foo').x} */ ({})` however this gets messy, repetitive and it's difficult to use algebraic types (e.g. `Event & { detail: string }`)

rangedbarbarian 12 hours ago||
Would `import type { x as y } from `./foo.x` not work?
c-hendricks 23 hours ago||||
JSDoc does not understand typescript syntax though? The typescript language server just kinda plows through/over JSDoc sure, but try getting JSDoc to parse some of the TS-ified things that JSDoc has alternatives for.

https://github.com/jsdoc/jsdoc/issues/1917

https://github.com/jsdoc/jsdoc/issues/1917#issuecomment-1250...

culi 23 hours ago||
tuples, `&` operator, and even generics all work perfectly well inside a `@type` declaration. For example:

```js

  /**
   * @type {{
   *   slug: `${string}_${number}`;
   *   id: number;
   * } & { status?: [code: number, text: string]; }}
   */
  const example = { slug: 'abc_34', id: 34 };
is the exact equivalent of

```ts

  const example: {
    slug: `${string}_${number}`;
    id: number;
  } & { status?: [code: number, text: string] } = { slug: 'abc_34', id: 34 };

For TS-specific keywords like `satisfies`, there's a corresponding JSDoc keyword like @satisfies. Generics use @template.

Is there any specific feature you think is not supported? I'm sure I could work up a TS Playground example.

c-hendricks 23 hours ago||
> Is there any specific feature you think is not supported

Yeah, uhm, most of what you've been posting? :). That JSDoc example above gives:

    ERROR: Unable to parse a tag's type expression for source file /Work/lol-jsdoc-why/index.js in line 1 with tag title "
    type" and text "{{  slug: `${string}_${number}`;  id: number;} & { status?: [code: number, text: string]; }}": Invalid type expre
    ssion "{  slug: `${string}_${number}`;  id: number;} & { status?: [code: number, text: string]; }": Expected "!", "$", "'", "(", 
    "*", ".", "...", "0", "?", "@", "Function", "\"", "\\", "_", "break", "case", "catch", "class", "const", "continue", "debugger", 
    "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "implements", "impor
    t", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", "supe
    r", "switch", "this", "throw", "true", "try", "typeof", "undefined", "var", "void", "while", "with", "yield", "{", Unicode letter
     number, Unicode lowercase letter, Unicode modifier letter, Unicode other letter, Unicode titlecase letter, Unicode uppercase let
    ter, or [1-9] but "`" found.
Edit: Also, your first edit says Webpack switched from TypeScript to JavaScript, but Webpack source was never written in TypeScript.
culi 22 hours ago||
We're talking about two different things here. All my examples work perfectly fine with TypeScript

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

You are attempting to generate documentation from jsdoc comments using an npm package that is also called "jsdoc". Ofc in this case "JSDoc is not TypeScript". That package only supports the subset of JSDoc that is relevant to it. Though I believe you can use TypeDoc instead if you want to generate documentation from JSDoc that contains typescript types.

In the post I made it explicit that I'm talking about intellisense, developer tooling, type checking etc. You can run `tsc` to do typechecking on a project typed with JSDoc like the examples I've given throughout this thread just fine.

I guess the difference here is I'm coming at this from the perspective of "what is TypeScript used for. Can JSDoc comments substitute that". And the answer is almost completely yes.

Also tbh I've never met anyone that uses that package to generate API docs. I don't think it's a very modern package: https://github.com/jsdoc/jsdoc/issues/2129

c-hendricks 21 hours ago||
Apologies, my first draft of that comment got deleted on a refresh (mobile) and my posted one left out how I'm probably being too pedantic: the official JSDoc is not TypeScript.

Your post is actually one of the more accurate ones compared to others that say "you don't need typescript" with the big caveat that you actually need a whole lot of the typescript ecosystem to make JSDoc work.

I just wish there was an official handover, or a more clear delineation between JSDoc and Typescript JSDoc Extensions.

culi 20 hours ago||
I think you have a valuable point. I kinda purposely avoided explicitly defining what JSDoc is. Instead I'm relying on "the JSDoc we're all familiar with". I said in the post that if your IDE is giving you intellisense from JSDoc comments then you are almost certainly already using TypeScript. That's about as close as I got to defining the JSDoc I'm talking about

But given that JSDoc doesn't have any sort of formal spec, I think the distinction you're making is more of a historical than a technical one.

hsbdhd 1 hour ago||
This was an interesting and useful post but you’re kind of losing the plot here in these comments defending against what should be a straightforward minor correction.

JSDoc has been around for more than twenty years and most implementations have never had most of the capabilities you’re describing.

It is actively misleading for you to say that JSDoc has these capabilities when you’re referring specifically and exclusively to TypeScript’s implementation of JSDoc, or you could say TypeScript’s alternative JSDoc syntax. Closure always used language like that in their documentation, and explicitly called out that they had diverged from standard JSDoc, as they should have. TypeScript’s own documentation sometimes refers to it as their superset of JSDoc, again recognizing that “JSDoc” actually does mean something specific and different.

The fact that there may not be a formal technical spec doesn’t mean you’re not wrong and it’s preposterous to suggest that.

There was established tooling and documentation going back 25 years, and it doesn’t somehow not count just because they didn’t give you a formal grammar…

k3vinw 20 hours ago|||
I’m curious how type checking is possible in a JDoc project. As far as I’m aware there’s no way to get type checking to work without tsc or a TypeScript LSP plugin.
culi 20 hours ago||
That is exactly how it works. Any IDE providing you intellisense is using the TypeScript language service. That's why this article is called "JSDoc is TypeScript". If you see a red squiggly because of your JSDoc comment, you are almost certainly already using TypeScript (without any .ts files)
k3vinw 15 hours ago||
Ha! I see I elicited the copy/paste response I’ve seen elsewhere. The gp comment I was replying to implied a JSDoc only solution (“all in JSDoc”), but given the response, clearly they’re still relying on the TypeScript language service (aka lsp plugin) to achieve type checking inside their IDE.

Wishful thinking on my part that an alternative solution for JSDoc based type checking exists :)

matt_kantor 9 hours ago||
Do you have a particular reason to care about the implementation details of your tooling's type checker?
k3vinw 7 hours ago||
Generally, no. Only in the case you had a requirement to exclusively use JavaScript as the programming language you might be in for an awkward time justifying that you added type checking to the project via the type checker component of TypeScript with JSDoc :)
phil294 4 hours ago||
Depends on your definition of "using" JavaScript. The main difference between common TypeScript and TS-based JSDoc is the need for an additional build step. Being able to ftp-upload your `.js` files and then be done with it is a remarkable advantage over Vite/Webpack/whatever in small to medium-sized projects. If editor based type support is sufficient to you (i.e. no headless checks), you won't need to install any TS packages at all, either. tsserver is still used in the background, but so are thousands of other binaries that keep your editor, OS and computer running, so I don't see that as an argument.
johnfn 23 hours ago|||
The article is an explicit response to the point you’re making.
phil294 4 hours ago||
> So (TS)JSDoc support is a relic from when Microsoft was trying to get market share from Google.

> Today in 2025, TS offers so much more than the (TS)JSDoc implementation. Generics, Enums, Utility types, Type Testing in Vitest, typeguards, plus other stuff.

None of that is true! Please don't share misinformation without looking it up first.

mirekrusin 1 day ago||
1. there are plenty things you can't express in jsdoc but can in typescript, flow did the right thing here where you have access to full language, not sure why typescript never did it, they could, with the same syntax flow is using

2. you can have navigation that goes to typescript file instead of definition, just arrange your exports in package.json correctly (first ones take precedence)

culi 1 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 1 day ago|||
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 1 day ago|||
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 22 hours ago|||
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 21 hours ago|||
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 20 hours ago||
> 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.

auxiliarymoose 1 day ago|||
You can use `&` operator to combine types. Works for adding fields, making branded types, etc.
g947o 1 day ago||
which is not the same as "extends".
spoiler 18 hours ago||
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 16 hours ago||||
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 1 day ago||||
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 1 day ago||
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 1 day ago||
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 1 day ago||
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 1 day ago||
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 23 hours ago||
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 1 day ago||||
and types can be written in .d.ts file(s), which can be used in jsdoc out of the box (no import needed)
mirekrusin 12 hours ago|||
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 1 day ago|||
You could, but in a large codebase with multiple contributors, it easily becomes messy and confusing.
afavour 1 day ago|||
ReturnType is TypeScript, no? You’re using JSDoc to express but it’s a TypeScript type.
culi 1 day ago|||
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 1 day ago|||
Isn't that the whole point of the article? For all intents and purposes, JSDoc IS TypeScript
creatonez 1 day ago|||
> there are plenty things you can't express in jsdoc but can in typescript

This isn't really true anymore, they have systematically added pretty much every type system feature to the JSDoc-like syntax.

g947o 1 day ago||
Having JSDoc-like syntax isn't the same as it being fully supported. If you have a large enough codebase, you'll likely find a few cases where things work in TypeScript but its equivalent somehow fails type check in JSDoc.
trekz 1 day ago||
> If you have a large enough codebase, you'll likely find a few cases where things work in TypeScript but its equivalent somehow fails type check in JSDoc.

You keep repeating this throughout the thread. Can you give an example?

spartanatreyu 1 day ago|||
Not that person, but is there an easy way to write something like a basic semver?

export type SemVer = `${number}.${number}.${number}`;

Could you extend it to work with regex groups like:

export const SemVerRegex = /^(?<major>0|[1-9]\d)\.(?<minor>0|[1-9]\d)\.(?<patch>0|[1-9]\d)(?:-((?:0|[1-9]\d|\d[a-zA-Z-][0-9a-zA-Z-])(?:\.(?:0|[1-9]\d|\d[a-zA-Z-][0-9a-zA-Z-]))))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;

Could the groups be extracted so you the type back if you ran the regex on a string like: "1.2.3", or "1.2.3-prerelease"?

matt_kantor 9 hours ago|||
I don't understand the second part of your comment (that's a value, not a type), but the first part looks like this:

    /** @typedef {`${number}.${number}.${number}`} SemVer */
Here's a playground: https://www.typescriptlang.org/play/?filetype=js#code/PQKhAI...
spartanatreyu 2 hours ago||
Thanks for the example, I had a good play around with it.

The second part of my comment is a value yes, but it's implicitly typed by typescript automatically. I was asking about how to use that type (and it's internals) in jsdoc.

g947o 1 day ago|||
it is possible to do many of these with @typedef, but it gets difficult with JSDoc very quickly. In TypeScript you can easily create multi-line type aliases. Not quite so in JSDoc.
g947o 1 day ago|||
https://news.ycombinator.com/item?id=46268701
measurablefunc 1 day ago||
TypeScript's type system is Turing complete so you have access to essentially unlimited expressivity (up to the typechecking termination depth): https://news.ycombinator.com/item?id=14905043
crummy 1 day ago||
> For packages typed with JSDoc, CTRL/CMD clicking on a function will take you to actual code rather than a type declarations file. I much prefer this experience as a dev.

ok i didn't think about this, that's an underrated benefit

agumonkey 1 day ago||
could be a customizable behavior in editor/ide though
g947o 1 day ago||
There is indeed an option in VSCode "typescript.preferGoToSourceDefinition" --

Prefer Go To Source Definition

Makes `Go to Definition` avoid type declaration files when possible by triggering `Go to Source Definition` instead.

IshKebab 1 day ago||
This works with Typescript too though?
filleduchaos 1 day ago||
It doesn't. You might be thinking of libraries that you wrote, not packages from e.g. npm, which are distributed as JavaScript + type definition files not as TypeScript code.
sthuck 1 day ago|||
It should work if the library was compiled with deceleration map option. Which most libraries are not and it's a shame.

It was added like 3 years ago which was probably a bit too late, not even sure why it's not the default. (File size?)

homebrewer 1 day ago|||
In real IDEs, not glorified text editors like VSCode, it does. I use this often in IDEA, it's muscle memory so I'm not even completely sure what to press, but it's likely "go to definition" and by default is tied to ctrl+alt+b.

IDEA adds its own analysis on top of that provided by the language server.

Works on JS + every variant of type definitions I've ever seen, among many other things (not only programming languages, but also database objects, etc).

matt_kantor 8 hours ago||
> IDEA adds its own analysis on top of that provided by the language server.

IDEA implements its own analysis and doesn't use tsserver at all. Its semantics diverge in subtle ways, I believe in both directions (some code that tsserver considers valid IDEA will consider invalid and vice versa).

zackify 1 day ago||
5 years ago I was at a meet up and the guy talking was saying how if you don't like typescript these jsdocs are the way to go. Had to explain to my employer attending that it is still typescript. Didn't seem to believe me and was super against typescript but not jsdocs lol
sntxcmp 1 day ago||
The difference is syntax compression imo.
TheRealPomax 1 day ago||
> Had to explain to my employer attending that it is still typescript

"is" is doing a lot of heavy lifting there: JSDoc and TypeScript are two different ways to explicit prescribe typing in a way that tooling can use to determine correctness. The TS syntax is _far_ more powerful, but JSDoc can do most of the common TS use cases, for folks who want to stay in JS land while still benefiting from type tooling (either invoked or straight up built into the IDE).

culi 1 day ago|||
> in a way that tooling can use to determine correctness.

As I pointed out in the article, the "tooling" is exactly TypeScript language services. If you are using JSDoc and you get squigglies or intellisense or any other similar features, you are using TypeScript.

You can copy-paste basically any bit of TypeScript into a JSDoc comment and it will work. JSDoc supports any non-runtime feature of TypeScript (so not enums). Even generics! You can even reference TypeScript utility types!

The whole point of this article was to correct the idea that JSDoc is not TypeScript. It absolutely is! There's almost nothing you can't define in JSDoc that you can't define in a .ts file. Albeit with a sometimes clunkier syntax

anovick 14 hours ago|||
That's absurd.

JSDoc is a comment formatting standard.

The tooling used to parse it is independent.

In the same vein, a file of ISO C++ (take any version) code is not "GNU C++".

matt_kantor 8 hours ago||
By "JSDoc is TypeScript", culi means "JSDoc syntax and TypeScript syntax are two alternative ways to describe types to the TypeScript compiler/language services, and the two syntaxes have nearly-identical expressive power".
KPGv2 21 hours ago|||
> If you are using JSDoc and you get squigglies or intellisense or any other similar features, you are using TypeScript.

This is true in the same way you are "using" C++ if you are on Windows. When most people say "use XYZ language" they mean "are personally writing code in XYZ language" rather than "under the hood my code is transpiled to this other language I don't write in"

Sammi 1 day ago|||
In practice today JSDoc 100% completely and utterly always is Typescript.

It might not have been so originally. It might still be possible to do differently. But in practice today you are getting Typescript in your JSDoc with the out of the box tooling that is everywhere.

hilarycheng 2 hours ago||
JSDoc is great. But remember, JSDoc is used for well trained developer. For those junior programmer, they will totally ignore it and write lots of shxt code. At least TypeScript can force them to use correct type like forcing them not to use "any" type.
llimllib 1 day ago||
counterpoint: JSDoc is not typescript

If you define a type in a file with @typedef, it is automatically exported and there is nothing you can do to control that: https://github.com/microsoft/TypeScript/issues/46011

I tried making a library this way and lacking control over the visibility of the exported types was really painful; it made my intellisense awful because every type I defined at the root was exported from the library

sureglymop 1 day ago||
I really like it for web components. Lately I have many "my-component.js" files and it's quite nice to just be able to copy them to new projects and have it all work without a build step. But I'm not sure I would use JSDoc over typescript syntax in a large project.
culi 1 day ago||
That's technically a fair complaint but feels like a minor nitpick. Just don't `@import` the types you don't want to use.
pjmlp 1 day ago||
TypeScript won over the alternatives, exactly because it is only a type checker, and not a new language.

Granted they initially weren't down that path, but they course corrected it on time, and not much people use stuff like enums in new code.

tobyhinloopen 19 hours ago|
> Take it from a massive TypeScript nerd: JSDoc is not an anti-TypeScript position to take. It is the same powerful static analysis without the build step.

Ahum https://nodejs.org/en/learn/typescript/run-natively

spoiler 18 hours ago||
Technically there's still a build step happening under the hood! But another benefit of shifting TS into JSDoc comments is that it also works in browsers. That being said, while I understand the aversion to unnecessary complexity, I'm personally fine with a build step to remove TS. It's a much nicer syntax IMO. And with tools like `swc` for type stripping, it's pretty snappy too.
eevilspock 18 hours ago||
that's limited to node. won't work in a browser.
More comments...