Javascript + JSDoc - A Step Function of Hatred
Rant incoming.
I tried. I really tried.
I'm writing a CLI tool to help with boilerplate for a Svelte HeadlessUI library and I was annoyed that I needed to
install ts-node, ts-node-esm, tsx, deno, bun, esbuild-runner, or myriad other tools instead of just being able
to node my-cli. I understand why, but it feels silly.
My Journey into Madness
When I did try ts-node (either via package.json scripts or directly at
the command line) because I’m using esm - that went to shit too. So I used ts-node-esm, but in a recent version of node - there were some
compatibility issues, so no dice there either.
I decided 20 minutes of duckduckgoing around wasn’t worth it - the script should be relatively straightforward and I’ve heard that JSDoc type annotations are good enough, and skips an annoying transpile step. If it’s good enough for Rich Harris, it’s sure as hell good enough for me.
Switching over to Javascript from Typescript, I noticed a couple of configuration quirks. No big deal, just needed to configure my JS settings to be similar to TS (for example, I almost always want to see the generated inlay type hints for non-explicitly-typed variables).
One nice thing about using JSDoc is that, since I was already adding JSDoc typing to most of my functions, I was a bit more likely to document that function's purpose and usage. I guess that's because the comment stub was already there, and it's incrementally more effort to write 1-2 lines of additional comments. But man, what a stupid friction barrier - I could automate/lint comment stubs if I wanted to, I've just never bothered to. Those extra comments are super important in this project, because the CLI mostly manipulates ASTs and AST transformations are so bloated and tedious to read.
For whatever reason, I couldn’t use .js files, as node or something
else complained that I needed to use cjs or mjs files directly, so
whatever. I'll just re-name the file and move on with my life, as this is so far unrelated to
getting code written.
Other than just looking a little bit jankier with the types specified above the function declaration instead of inline, JSDoc seems fine. Weirdly, I’m not getting linting errors when I fail to import something, but I think that’s just a VSCode configuration thing. Since I’m just packing all of my functions into the same file until it gets annoying, this is a problem for future me.
30 minutes later
/** @type {string[]} */
const svelteProps = []; ...Well that’s gross...
Fortunately, it doesn’t look like I’ll have to type too many variables directly, as type inference is pretty solid - but yuck nonetheless.
A few hours later
Alright, I think I’m coming around to this Javascript + JSDoc business for simpler things like CLIs.
I still think JSDoc results in some extra visual clutter and I personally would prefer my types inline rather than above my function, but those are nitpicky problems. I'm just not used to how that looks yet, so it's a bit jarring - granted, this would be the only language I’ve worked in with non-inline type hints, but whatever.
It's super nice to just be able to node myfile and watch it run instantly without
transpiling. That's enticing...
A few weeks later
I miss Typescript and I hate Javascript.
I was okay using Javascript + JSDoc until EXACTLY the moment I wasn’t. It wasn’t gradual, it was instantaneous. A step function of hatred.
I’ll be the first to say that it’s likely a lack of familiarity and skill issues, but as soon as code is harder to run or I have to check for runtime problems that should just be type checked, I’m out.
On this CLI, I’ve only put about 15 hours away from Typescript, but I’ve also given JSDoc a shot on some private projects and the same sorts of problems crop up and they’re just not worth the headache.
These problems particularly started to crop up when splitting up code into multiple
files/functions and unit testing them. For the first time in years I got hit by runtime import
errors because I guess my linter wasn't setup correctly (or maybe VSCode was still
mis-configured, even though I did get SOME import warnings inside the IDE). Then we have the
abundance of visual clutter JSDoc was causing. I don't know why it bugs me so much, but it turns
out that I didn't just get used to it over time. The biggest pain points were the variables
typings, but at some point, I just started seeing any more often in my code hints.
Where did they come from? Why wasn't my inference across modules always working? No idea.
The visual clutter of JSDoc is particularly noticeable in utility functions. Take a look at some Svelte examples where the JSDoc annotations exceed the function body:
/**
* @param {number} t
* @returns {number}
*/
export function sineInOut(t) {
return -0.5 * (Math.cos(Math.PI * t) - 1);
}
/**
* @param {number} t
* @returns {number}
*/
export function quintOut(t) {
return --t * t * t * t * t + 1;
}
/**
* @param {number} t
* @returns {number}
*/
export function sineOut(t) {
return Math.sin((t * Math.PI) / 2);
} As Typescript? My eyes don't glaze over as much:
export function sineInOut(t: number): number {
return -0.5 * (Math.cos(Math.PI * t) - 1);
}
export function quintOut(t: number): number {
return --t * t * t * t * t + 1;
}
export function sineOut(t: number): number {
return Math.sin((t * Math.PI) / 2);
} Another annoyance I ran into while reviewing some Svelte5 code. I can't recall exactly where it was, but most (all?) of the exported functions are well-type-annotated, but then there are a few helper or private functions which aren't. Inside of an IDE, this wouldn't be an issue because of type-inference. While trying to read the code on GitHub or look through pull requests? A mild nightmare.
To be clear, I’m sure the majority of these problems are me-issues... I'd fully believe anyone who said that they've never run into these kinds of problems in all their time with Javascript + JSDoc. But, my simple retort would be, I've never run into anything like this after years of using Typescript.
Onward and Upward
But either way, the underlying problem that kicked off this Javascript + JSDoc exploration ended
up being solved with 1 dependency (tsx) and 1 script added to my package.json. Is there a performance hit from transpiling my project? Maybe, but I've
never noticed it.
While this was an annoying experiment, I can better see the arguments of the people who are
abandoning Typescript for JSDoc. The transpiler is still a "thing" and it can be a bit
of a pain to set up, but it's not a huge deal. Maybe when a project gets sufficiently large, it
becomes a huge deal and in those cases I can definitely see the appeal of just being able to node and go. This won't be my last time trying JSDoc, but it will be a while before
I try it again.
For what it's worth, one big argument I’ve seen against Typescript is that it causes a lot of type gymnastics - because you’re trying to write production code and satisfy the type checker at the same time. Or, the availability of types makes some developers want to over-type their code which can lead to re-factoring issues. To me, that screams "skill issue".
