Top
Best
New

Posted by culi 12/14/2025

JSDoc is TypeScript(culi.bearblog.dev)
208 points | 275 comments
Waterluvian 12/15/2025|
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 12/15/2025||
> 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 12/15/2025||
"who wrote this garbage?... oh yeah that was me"
pavel_lishin 12/15/2025||
Nothing puts you in your place like a `git blame`.
john01dav 12/15/2025|||
> - 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 12/15/2025|||
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 12/15/2025|||
is that not exactly "deciding how much to prove it?"
culi 12/15/2025|||
> 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 12/15/2025|||
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 12/15/2025||
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 12/15/2025|||
> 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 12/15/2025|||
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.

KPGv2 12/24/2025||
> TypeScript is indeed Javascript

No, no. JavaScript is TypeScript. TypeScript is not JavaScript.

> type annotations are not code

Of course they are. If they weren't code, then a malformed type would not trigger an error. You can actually solve the eight-queens problem with TS's type system alone.

https://lemoine-benoit.medium.com/eight-queens-puzzle-in-typ...

The type system itself is Turing-complete!

egeozcan 12/15/2025|||
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 12/15/2025||
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)

jama211 12/15/2025|||
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 12/15/2025||
> 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 12/15/2025|||
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 12/15/2025||
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/15/2025|||
> 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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
> 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 12/15/2025||
> 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 12/15/2025||
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 12/15/2025||
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 12/16/2025||
> 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 12/16/2025||
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/16/2025||
Yeah, you are right that ease of drilling down values through the layers is balanced by the need to remember which places actually do something with the value.

While I always liked dynamic languages and probably wrote most of my code in them I don't think they are useful anymore. Tooling got great and LLMs need every chance they get of verifying stuff they hallucinated. At this point I wouldn't mind a language that's strict about typing but other things as well as ownership, protocols, mandatory assertions, tests, code coverage, even some formal verification. As long as I don't have to write them, but LLM does and uses them to check its work, I'm really fond of it.

scotty79 12/15/2025|||
Yeah, tooling for strongly typed languages is way better than 20 years ago. They are getting very being usable.
jama211 12/16/2025|||
It’s more work, if it wasn’t there’d be no debate.
christophilus 12/15/2025|||
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 12/15/2025|||
> 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 12/15/2025||
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.

dominicrose 12/16/2025||
Well we take risks in either direction. The risk of using Java is becoming bored to death and having delays. The risk of PHP is that you know bugs are going to happen in production, even if you have something like Sentry to monitor them, even if they're not critical bugs.
scotty79 12/15/2025|||
> 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 12/15/2025||
As I understand that is the core design feature of any strongly typed language.
matt_kantor 12/15/2025|||
I believe they're talking about gradual typing[0].

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

scotty79 12/15/2025|||
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.
fulafel 12/16/2025||
Nitpick: In the "static, dynamic, strong, weak" quad, C/C++ are considered weakly but statically typed because you can nonchalantly bypass the type system.

In this case you'd need to be pretty explicit about the bypassing but you could write

  int a = 1;
  strcpy((char *)&a, "hi");
(and it'll even kind of work in this instance since "hi" fits in an int, if on your C/C++ implemnentation int happens to be sized 3 bytes or more)
user3939382 12/15/2025||
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 12/14/2025||
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 12/14/2025||
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 12/14/2025||
I haven't run into any steps that require one, there's always alternatives.

Do you have anything specific in mind?

webstrand 12/15/2025||
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 12/15/2025|||
If there is one thing I don't miss using WebComponents is JSX. lit-html is much, much better.
claytongulick 12/15/2025||
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 12/15/2025||
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 12/15/2025||
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/15/2025|||
I recently used Preact and HTM for a small side project, for the JSX-like syntax without a build step.
odie5533 12/15/2025|||
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 12/15/2025||
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 12/14/2025|||
Webcomponents are a pain in the ass to make, though. That is, sufficiently complex ones. I wish there was an easier way.
eric-p7 12/15/2025|||
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 12/15/2025||||
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

WorldMaker 12/16/2025||||
I find Web Components aren't as much of a pain to write if you ignore the Shadow DOM. You don't need the Shadow DOM, it is optional. I don't think we are doing ourselves too many favors in how many Web Component tutorials start with or barrel straight into the Shadow DOM as if it was required.
prisenco 12/14/2025||||
They could have better ergonomics and I hope a successor that does comes out but they're really not that bad.
brazukadev 12/15/2025||
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 12/15/2025||
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 12/15/2025|||
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 12/14/2025|||
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.

mb2100 12/15/2025|||
This. Or use ts-blank-space if you prefer TypeScript over JSDoc. That's what we do in https://mastrojs.github.io
winrid 12/15/2025|||
TS is worth the build step.
prisenco 12/15/2025||
JSDoc is TypeScript.
KPGv2 12/15/2025||
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.
pjmlp 12/15/2025|||
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.

fergie 12/15/2025|||
> 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.

mmcnl 12/15/2025|||
You don't need a build step anymore with TypeScript since Node 24.
prisenco 12/15/2025||
I'm referring to client-side javascript.
mmcromp 12/15/2025|||
Why? The half a second for the HMR is taking up too much your day?
prisenco 12/15/2025|||
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 12/15/2025||
> 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 12/15/2025||
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 12/15/2025|||
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 12/15/2025||
Came here to write this exact sentiment. Not everything needs a massive build pipeline.
etoxin 12/15/2025||
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 12/15/2025||
> 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 12/15/2025|||
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 12/15/2025||
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 12/15/2025||
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/15/2025||
Would `import type { x as y } from `./foo.x` not work?
c-hendricks 12/15/2025||||
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 12/15/2025||
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 12/15/2025||
> 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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
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 12/16/2025||
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 12/15/2025|||
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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
Do you have a particular reason to care about the implementation details of your tooling's type checker?
k3vinw 12/15/2025||
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 12/15/2025||
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 12/15/2025|||
The article is an explicit response to the point you’re making.
phil294 12/15/2025||
> 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 12/14/2025||
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 12/14/2025||
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 12/14/2025|||
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 12/14/2025|||
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 12/15/2025|||
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 12/15/2025|||
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 12/15/2025||
> 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 12/14/2025|||
You can use `&` operator to combine types. Works for adding fields, making branded types, etc.
g947o 12/14/2025||
which is not the same as "extends".
spoiler 12/15/2025||
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...
g947o 12/14/2025||||
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 12/14/2025||
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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
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 12/15/2025||
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

mirekrusin 12/15/2025||||
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.

efortis 12/14/2025||||
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/15/2025|||
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 12/14/2025|||
You could, but in a large codebase with multiple contributors, it easily becomes messy and confusing.
afavour 12/14/2025|||
ReturnType is TypeScript, no? You’re using JSDoc to express but it’s a TypeScript type.
culi 12/14/2025|||
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 12/14/2025|||
Isn't that the whole point of the article? For all intents and purposes, JSDoc IS TypeScript
creatonez 12/14/2025|||
> 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 12/14/2025||
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 12/14/2025||
> 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 12/15/2025|||
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 12/15/2025|||
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 12/15/2025||
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.

matt_kantor 12/16/2025||
> it's implicitly typed by typescript automatically

What do you mean by this? Can you share an example?

Regular expressions do not act as type guards, and there's no way to express a type that allows one regular expression but rejects another (they're all just `RegExp`): https://www.typescriptlang.org/play/?target=5#code/MYewdgzgL...

spartanatreyu 12/16/2025||
You wouldn't use `test()` like in your example.

`test()` only returns a boolean, you want to look at `exec()` which returns the result of the regular expression (typed as: `RegExpExecArray | null` which you can narrow down to `RegExpExecArray` by checking if the result is null or not).

RegExpExecArray gives you a structure that looks different between the jsdoc version and the typescript version.

The typescript version has `.groups` inside RegExpExecArray.

You can use that as is, or you can add some extra type utilities to extract the named groups from inside the regex. (If you look inside typescript's issues on github you'll find a whole bunch of them that people have wanted typescript to include by default).

There's a few regex PRs to add extraction and syntax checking to typescript by default, but they're delayed until after the compiler switch from ts to go. (but there's porting to go in the PRs anyway).

matt_kantor 12/16/2025||
Thanks for the reply.

> RegExpExecArray gives you a structure that looks different between the jsdoc version and the typescript version.

> The typescript version has `.groups` inside RegExpExecArray.

The JSDoc version has `.groups` too, and it appears to be typed identically to the TypeScript version given similar configuration.

Here's the TypeScript version: https://www.typescriptlang.org/play/?target=5&strictNullChec...

And here's the JSDoc version: https://www.typescriptlang.org/play/?target=5&strictNullChec...

Could you show me what you mean in the playground? You can change it to JSDoc mode by selecting "JavaScript" in the "Lang" dropdown in the "TS Config" tab.

spartanatreyu 12/18/2025||
Your examples has groups only typed as { [key: string]: string }.

It isn't narrowed down to the actual named capture groups inside the regex.

For the whole semver example, we'd want something typed as: { major: string, minor: string, patch: string }.

(sidenote for this semver example and not regex in general: they will be strings because a major of "01" and a patch of "3-prerelease" are both valid. You can parse them into ints later depending on what you're actually doing with the semver)

---

For an example of a type narrowed regex in the ts playground, I'll take the lazy route and point you to an example someone else has made:

https://www.typescriptlang.org/play/?ssl=1&ssc=39&pln=1&pc=3...

You can use a utility library (e.g. ts-regexp, arkregex, etc...), you can use one of the utilities that are shared around the typescript spaces online for the one part you need, or you can make your own utility type.

---

For my use-case, I just want to know that I can change either my code or regex and I'll be alerted before runtime or buildtime if I've forgotten to account for something.

To carry on with the semver example, if a fourth piece of data was added to the incoming string (e.g. product name, build timestamp, build variant, etc...) and I forgot to update both the code and the regex together and instead only updated one, I'd want an alert in my IDE to tell me that I forgot to update the other one.

matt_kantor 12/18/2025||
The ts-regexp package `infer`s out of template literal types to do its magic. That's possible from JSDoc too: https://www.typescriptlang.org/play/?filetype=js#code/PQKhCg...

The behavior of ts-regexp is not especially related to the `RegExpExecArray` type. That package uses unsafe assertions to implement its API.

The only thing that requires TypeScript syntax in the playground you linked is the non-null assertions. Here's that same code in JSDoc mode with the non-null assertions removed (`strictNullChecks` is disabled instead): https://www.typescriptlang.org/play/?filetype=js#code/JYWwDg...

---

> For my use-case, I just want to know that I can change either my code or regex and I'll be alerted before runtime or buildtime if I've forgotten to account for something.

If you can show a concrete example what you want using TypeScript syntax then I'm pretty sure I could port it to JSDoc syntax. (To be clear I'd personally much rather write the TypeScript syntax as I find JSDoc comments annoying & verbose, but they're plenty powerful, being nearly isomorphic to TypeScript annotations.)

g947o 12/15/2025|||
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 12/15/2025|||
https://news.ycombinator.com/item?id=46268701
measurablefunc 12/14/2025||
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 12/14/2025||
> 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 12/14/2025||
could be a customizable behavior in editor/ide though
g947o 12/14/2025||
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 12/14/2025||
This works with Typescript too though?
filleduchaos 12/14/2025||
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 12/14/2025|||
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 12/14/2025|||
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 12/15/2025||
> 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 12/14/2025||
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
TheRealPomax 12/14/2025||
> 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 12/14/2025|||
> 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

KPGv2 12/15/2025|||
> 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"

anovick 12/15/2025|||
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 12/15/2025||
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".
anovick 12/16/2025||
I understood it that way too. Only that it fails in that interpretation.

Just because currently the most popular method for parsing JSDoc utilizes TypeScript-related tooling doesn't mean we should associate them with each other.

To my previous example, an ISO C++ can be compiled by GCC but also can be compiled by Clang (and many other compilers).

matt_kantor 12/16/2025||
Perhaps I should've expanded further to "the specific JSDoc dialect supported by the TypeScript compiler and TypeScript syntax are two alternative ways to …".

Unlike ISO C++ I'm not aware of any standard for what JSDoc is outside of specific implementations. There's a bunch of stuff documented on https://jsdoc.app/ but it only partially overlaps with the JSDoc syntax that TypeScript uses, and is not what the article is talking about.

The situation is not unlike Markdown before the CommonMark standard (except with less pressure to standardize, because most codebases are written with specific tooling in mind). Maybe we should refer to it as "TypeScript-flavored JSDoc".

Sammi 12/14/2025|||
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.

TheRealPomax 12/16/2025||
Welcome to the "a cat [1] has four legs [2], and therefore everything with four legs [3] is a cat [1]" fallacy. That's not how that works.

JSDoc is its own spec, and is not typescript, and is absolutely not at type annotation parity with typescript. Plenty of things that are hard to type in TS that are impossible to annotate use JSDoc.

So: just because tsc, the tool, can read in typing information that was written in JSDoc, does not make JSDoc typescript. tsc simply supports JSDOc. Very handy! Not the same thing in the slightest.

1. "typescript"

2. "can be read by tsc"

3. "anything that tsc can read"

Sammi 12/17/2025||
You are mixing theory with practice. In theory felines can be both cats and tigers. In practice the felines walking around your neighborhood are all cats.
TheRealPomax 12/17/2025||
...what?

No of course not, that's an absolutely ridiculous thing to say. ∀A -> B does not mean ∀B -> A, it only means ∃B -> A.

tsc, the executable, not only supports TypeScript, the language, but also knows how to read JSDoc when you're not giving it TypeScript. It supports JSDoc for code that isn't TypeScript. I'd repeat that a few more times but hopefully twice is enough to make the point obvious.

Sammi 12/17/2025|||
Sure. In theory people could be doing that. But for the third time: what is happening in practice?

What people _could_ be doing vs what they _are_ doing was explicitly my point, so please stop pretending it wasn't, less we have to repeat this merry go round.

TheRealPomax 12/18/2025||
If the question is about whether JSDoc is TypeScript, and you want to turn it into a different question, maybe your question belongs in a different thread.

JDoc, the spec and docs-in-practice, is not TypeScript, the language and tooling ecosystem in practice, and does not have type annotation parity with TypeScript (so it's not even "the choice is arbitrary in practice because they do the same thing").

JSDoc is simply a supported spec by the TypeScript tooling. Saying "JSDoc is TypeScript" when you're a programmer, especially one that uses one or the other, is idiotic: your job relies on precision in both code and terminology.

sntxcmp 12/14/2025||
The difference is syntax compression imo.
llimllib 12/14/2025||
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 12/14/2025||
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 12/15/2025||
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 12/14/2025||
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.

akst 12/14/2025||
I'm actually using JSTypes in app, I don't mind it.

I choose to use it because I didn't want to deal with a build step for a smaller project. The project has grown and I am looking at adding a build step for bundling but still not too worried about using JSDoc over TS.

This might be my config, but one thing that does annoy me is whenever I define a lambda, I need to add an doc type. I guess if that's disincentivising me from writing lambdas maybe I should just add a TS compile step lol.

----------------------

Here's an example - I got some config typed with this function https://github.com/AKST/analysis-notebook/blob/c9fea8b465317... - Here's the type https://github.com/AKST/analysis-notebook/blob/c9fea8b465317... - And here's something to generate a more complicated type for defining config knobs https://github.com/AKST/analysis-notebook/blob/c9fea8b465317...

g947o 12/14/2025|
These days you can run TypeScript files as-is (type stripping) for testing in many runtimes, and for bundling, most bundlers support using a ts file as the entry point directly.
akst 12/15/2025||
yep! However this is a website I made for hosting my own notes in the browser browser (sorry for the load times, no bundler so each file is a seperate request)

https://akst.io/vis/20250601-complex-geo/?app=unsw.2102.01-1

That said, when I write tests, I write them in Typescript for that reason.

tobyhinloopen 12/15/2025|
> 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 12/15/2025||
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 12/15/2025||
that's limited to node. won't work in a browser.
tobyhinloopen 12/19/2025||
Oh right I forgot Javascript also runs in a browser
More comments...