TypeScript is developer experience
It’s 2023, and it’s undeniable that TypeScript has become a de facto standard for JavaScript development. There are several reasons for its impact and popularity. Still, one of the main ones is that it provides a better developer experience by adding inline documentation, auto-completion, and type-checking, even if the consumers of our code don’t use TypeScript.
Nonetheless, folks developing libraries and frameworks sometimes want to avoid going through the friction introduced by TypeScript, such as the need to compile the code, the extra configuration needed to make it work, and the strictness of the type system. This aversion is understandable, but in this article, we’ll cover how we can get the benefits of TypeScript without switching completely.
The power of JSDocs
JSDocs is a standard for adding inline documentation to JavaScript code that predates TypeScript and was a strong influence for its type system. As such, we can use JSDocs to add inline documentation and type-checking to our code when working with vanilla JavaScript. Let’s see an example with a simple greeting function:
By adding a block comment that starts with /**
, we can create a JSDoc block, and then we use the @param tag to type the function arguments that tools like VSCode will use to get the types the same it would do with TypeScript, but without it. Want to make that argument optional? Just wrap it in square brackets:
We aren’t limited to only using primitive types such as string
, but also we can create more complex types using the @typedef tag:
Mixing a little TypeScript
This last type declaration using @typedef
might feel like it adds a lot of “noise” in our code, so one solution is to use .d.ts
files next to our .js
files to declare complex types such as User
. Let’s say then that we put this code in a types.d.ts
file:
And then, the JSDoc can import the type it needs:
That // @ts-check
is annoying
You might have noticed that we are using a // @ts-check
comment in each file to enable the “power of TypeScript” on it. This comment is only necessary because we are avoiding configuration files, but then again, we can add a little TypeScript without compiling our code. Therefore, we only need one extra file in the root of our project named tsconfig.json
with the following content:
This file will ensure that TypeScript will check our JavaScript files and remove the need for the @ts-check
comment. On top of that, I recommend setting the strict
option to true
to get the most out of TypeScript and avoid the most common pitfalls of untyped code.
Ready to publish
Our code is almost ready to give all the benefits of type checking to our consumers (either if they also use JavaScript or if they use TypeScript). We only need to generate the type files in our prepublishOnly
script in package.json
:
This script will generate a .d.ts
file for each .js
file in our project. The last step is to add a types
field to our package.json
to point to the generated types:
And that’s it! We can now publish our code to npm, and our consumers will be able to get the benefits of TypeScript without having to switch to it.
If you want to see a real-life example, you can check out this open-source package I maintain that uses this approach to provide config files for all my other projects: configs.
Conclusion
I’m a big fan of TypeScript and use it in all my projects, but this is still a good compromise to provide the same benefits without switching completely.
The point of this article is that there’s no good excuse to avoid DX improvements, so if you have any projects out there that you know are being used by others, it may be time to make them more friendly to your consumers.