Going full TypeScript for a small project or a simple script often feels like overkill. Changing every file extension and wrestling with a complex build process sometimes just isn't worth the effort if you're only trying to catch basic bugs.
There is a middle ground that works well: stick to raw JavaScript, but use JSDoc to provide types. This keeps the project structure lightweight while still getting your editor to help you while you code.
The catch is that you have to enable checkJs in your jsconfig.json or
tsconfig.json. Without that setting, JSDoc is basically just documentation or
"cosplay": just regular comments that the machine ignores. But once checkJs is
on, the editor starts flagging mismatches. It’s great for catching stupid
mistakes, like passing arguments in the wrong order or trying to access a
property that doesn't exist.
To keep things efficient, I use @typedef so I don't have to redefine the same
object structures over and over. A really powerful trick is using
@returns {value is T} for type guards. At that point, JSDoc stops being just a
note and actually becomes a tool for your logic.
That said, this approach has its downsides. JSDoc starts getting exhausting once you hit complex stuff like generics. If you need dynamic types or mapped types, the syntax feels like you're swimming against the current. Plus, the inference in JSDoc isn't as strong as native TypeScript; you often find yourself writing more annotations because the engine isn't as smart at guessing the context.
There's also the risk of annotations going stale. If you change a function but
forget to update the JSDoc, that incorrect info can be more dangerous than
having no types at all. That's why I always make sure checkJs is part of the
CI to keep the type integrity intact.
So, when do you stick with JSDoc and when do you just give in to TypeScript?
I stay with JSDoc if I want to keep everything as pure .js files without the
headache of emits or sourcemaps, especially if the API is small and the types
are simple.
But I'll switch to TypeScript immediately if my annotations are getting longer than the actual function body, or if I need to share highly complex types across packages. At that stage, we're basically already using a compiler through our editor; we're just refusing to save the types in a dedicated syntax.
Not everything needs a full migration to TypeScript today. The important thing is just having some sensible safeguards in place.