Remix.run Logo
apatheticonion a day ago

Personally, given the limitations of JSDoc, I'd like to see a header-file like definition system for build-less applications (like libraries).

    /
      index.js
      index.d.ts
Where values in `index.js` can be typed in the header file with complete TypeScript syntax and those types are present in the target file via intellisense and type checking

    // index.js
    function useStr(x){} // has intellisense for "string"

    useStr("Hello")
    useStr(42) // IDE and type checker error
And

    // index.d.ts
    declare function useStr(x: string): void
MrJohz a day ago | parent [-]

This can be done, and is done in some projects, but the problem is that you need to manually maintain both sets of files, which can easily go out of sync if you're not careful. You also lose out on the ability to check your own code - using the header file you've written there, you can't really check the implementation of `useStr` to check that it works.

The advantage of JSDoc is that the TypeScript compiler treats it as equivalent to TypeScript source code, which means you've still got access to type checking inside your functions.

One thing I've seen in a few places is still to have DTS files that declare all sorts of types that are used in the application, and then have those files imported by JSDoc comments. That way, you've got TypeScript syntax for your types, which tend to get bulky and difficult to read in JSDoc syntax, but you still don't need a build system because the DTS imports are ignored completely at runtime.

apatheticonion a day ago | parent [-]

You can define a declaration file alongside its target however the target does not use the types defined within its declaration within itself - only consumers see the types.

There are three issues with that.

The first is that JSDoc doesn't support everything you need in TypeScript and there is a lot of inlining (like typedef causes collisions, there's no importing a type without also re-exporting it from the importing file unless you inline the import)

The second is that JSDoc isn't picked up from imported libraries (or at least I don't think it is?)

Lastly, you still need a transpiler to remove the comments and build d.ts files.

In the end, JSDoc isn't practical for projects. The header file strategy means you don't need to transpile ever (only a typechecker) and you'd get the full suite of TypeScript functionality - at the cost of synchronization overhead.

For a project using JSDoc, you'll graduate to TypeScript when there is sufficient complexity anyway. Migrating from a d.ts file is way easier (potentially automatable) than migrating from JSDoc.

MrJohz a day ago | parent [-]

The problem with the header file strategy is that TypeScript doesn't have total type inference. That approach works e.g. in OCaml, where you can (optionally) type the boundaries of the function and everything inside the function can always be inferred. But TypeScript works differently and doesn't support that approach. So fundamentally, what you're describing isn't really possible without using a very restrictive subset of TypeScript that can be totally inferred or having very broad type definitions.

So even if TypeScript did check the header file against the implementation, you'd still need additional annotations inside the implementation file. At that point, why not put all the annotations inside the implementation file directly?

Regarding your points:

1. JSDoc does support everything that TypeScript supports (that's the point of this article), although it does not necessarily support it as cleanly, hence why projects like Svelte typically use DTS files to define the project's types, and have those be imported inside JSDoc annotations. It's not perfect, but it gets you a long way.

2. You're right, JSDoc isn't picked up from imported libraries, but if you're publishing a library, you'll have a build script there, and at that point it's typical to generate the DTS files and pack those with the source code. That seems fairly reasonable to me — while developing, you don't need to worry about building files, but then when packaging and releasing you do.

3. You don't need a transpiler with JSDoc, the point behind JSDoc is that it's literally just the pre-existing JS documentation syntax. You can (and should) leave the comments in. Even if you're using TypeScript syntax, you should have JSDoc annotations (although adding the types to those annotations is redundant in that case). You can also just include the DTS files. As long as they're only imported from JSDoc, the runtime won't see them.

Personally, having tried out JSDoc-based TypeScript, I'd rather just use the native type stripping in Node for development, and keep with TS syntax, but there are large projects that have made the opposite choice, most noticeably Svelte. So I don't think it's a given that JSDoc users will inevitably graduate to TypeScript (especially when projects have gone in the opposite direction).