Over the past couple of days, an article about the next major version of Svelte blew up on twitter, and caused lots of discussion. The article states:
The team is switching the underlying code from TypeScript to JavaScript.
Which, to be fair, is a bit misleading. Technically, the article is not wrong, the team is switching the underlying code from TypeScript to JavaScript. However, they're not dropping typechecking from their code. They're just moving from writing TypeScript source code directly, to writing JavaScript source code using JSDoc in combination with .d.ts
files. This allows them to:
- Write typesafe code, with TypeScript doing the typechecking
- Write and ship plain JavaScript
- Skip TypeScript's compilation step
- Still provide types for their end users
What's interesting about this discussion is that a lot of people found this to be very upsetting, and twitter blew up with discussion about typechecking. We saw the exact same thing happen when the ESLint team announced they were not interested in using TypeScript for their rewrite of ESLint, but instead were choosing the same approach the Svelte team is going for; plain JavaScript with JSDoc for typechecking. In these twitter discussions it has become very clear that lots of people, even some of those who call themselves "educators", don't understand how capable JSDoc actually is and will unfortunately just spread blatant untruths about this way of working.
It should be noted here that neither of those teams are disregarding typesafety. They just chose a different way of utilizing typesafety, without having to use a compile step to achieve this. This is a preference. There is no right or wrong answer; you get typesafety by using TypeScript in either approach. This discussion and these decisions are not about not using TypeScript. And unless you're directly working on, or contributing to, either of those projects, these decisions do not involve you. It is, frankly stated, none of your business. If you want to use TypeScript with a compilation step; go for it! There's no need for animosity. These projects still utilize TypeScript to ensure typesafety in their code, and can still ship types to their end users. In this blog we'll take a look at the benefits of skipping TypeScripts compilation step, clarify some of the myths I've seen spread around, and emphasize the importance of understanding and being respectful of different preferences and methodologies.
In this blog, I won't go into detail on how to setup your project to enable typechecking with JSDoc, there are many great resources on that like this one here
Before we dive in, I'd like to reiterate one more time that using types via JSDoc allows people to still write typesafe JavaScript, by using TypeScript's typechecker. It just skips the compilation step. You'll also be able to still use .d.ts
files when necessary (but you don't have to!), and provide types for your users. And yes, this approach is still using TypeScript.
Benefits of skipping a compilation step
Compilation or transpilation steps in the JavaScript tooling ecosystem are often a bit of a necessary evil, like for example transpiling JSX, or in this case TypeScript code. They're often not as fast as we'd like them to be, and often take a bit of fiddling with configuration (although it should be noted that lots of tooling has improved in recent years) to get your setup working just fine. Not only for building your projects for production, but also having everything setup correctly for your local development environment, as well as your test runner. While compilation or transpilation offers conveniences (writing JSX source code, instead of React.createElement calls manually, or writing types in your source code directly), some people find these compilation steps to be undesirable, and prefer to skip them where possible.
Skipping a compilation step, in the context of TypeScript usage, has several benefits. It makes your code runtime agnostic; your code will run in Node, Deno, the browser, Worker-like environments, etc. Some of these environments, like Deno, support running TypeScript natively (which has a whole other set of worrisome implications*). Some of those environments, like the browser, don't (not until the types as comments proposal lands anyway). This may or may not be an issue for you depending on what you are building, but again, some people find this to be preferable.
- It has been pointed out to me that Deno will now run with
--no-check
by default, which mitigates some of it's issue. However, the issue still exists when using--check
.
If your code is runtime agnostic, it also allows you to easily copy and paste snippets of code into REPLs. Shipping native JavaScript also simplifies debugging, consider the following example: You've shipped your package as native JavaScript. Somebody installs your package and discovers a bug. They can just go into their node_modules and easily tweak some code to try to fix the bug, without having to deal with transpiled code, source maps, etc.
An added benefit of using JSDoc that I've personally found (this is a personal preference), is that the barrier to documenting my code is much lower as opposed to TypeScript. Consider the following example:
Admittedly, a function named
add
probably doesn't require a whole lot of documentation, but for illustration purposes.
When I type /**<ENTER>
on my keyboard, my editor will already scaffold the JSDoc comment for me, I just have to write my types. Note that the return type can be omitted, because TypeScript will still correctly infer the return type from the code. While I already have the JSDoc comment here anyway, I might as well add some documentation for it! Easypeasy.
Myths
Using JSDoc is unmaintainable
Some people on twitter have expressed concerns about the maintainability of using JSDoc for types, and claim that using JSDoc is only viable for small projects. As someone who maintains many projects at work (some of which are large) that utilize types via JSDoc, I can tell you this is simply not true. It can be true that if you're only using JSDoc to declare and consume your types, this can sometimes become a bit unwieldy. However, to avoid this, you can combine JSDoc with .d.ts
files. Declare your types in a .d.ts
file:
./types.d.ts
:
export interface User {
username: string,
age: number
}
And import it in your source code:
./my-function.js
:
/**
* @typedef {import('./types').User} User
*/
/**
* @param {User}
*/
function foo(user) {}
No type inference or intellisense
Some people seem to think that using JSDoc somehow will cause you to lose type inference. As already demonstrated earlier above, this is also not true. Consider the following example:
The reason for this claim seems to be that people don't understand that when you're using JSDoc for types, you're still using typescript. TypeScript is still doing the typechecking.
Manually writing types is bothersome
Some people claimed that writing types manually is bothersome. I can only assume that this is a case of preference, or perhaps its not clear to those people that you can still .d.ts
files. Some people will prefer example B
over example A
. This is fine. Both can be used when using JSDoc for types.
example A
:
/**
* @typedef {Object} User
* @property {string} username
* @property {number} age
*/
example B
:
export interface User {
username: string,
age: number
}
But that still uses TypeScript!
Yes, this is the point.
In conclusion
Finally, and I'm repeating myself here, using TypeScript without compiling your code is a preference. There is no right or wrong answer and I challenge anyone who is skeptical of this approach to be a little bit more open minded and give it a try some time when you're starting a new project, you might find it's actually a quite nice approach of utilizing types. And if you end up not liking it, that's fine too!
Latest comments (123)
I have very little experience with JS and TS but I do have a lot of experience with Python, and I keep coming back to this blog over and over again. I think it would be cool if we could write good old non-typed Python, but add fully typed "header files" + include types in docstrings in a way they were understood by type checkers, essentially mirroring the workflow you describe here. The current situation is that stub files were never fully supported by MyPy github.com/python/mypy/issues/5028 and that we resort to brittle hacks to merge signature types with docstrings github.com/tox-dev/sphinx-autodoc-... . I wish we could evolve faster.
In any case, thanks a lot for sharing!
I strongly prefer the inline TS syntax (it's both more readable and more writable for me) but that requires the build step, that's all the matter of choice and enhancing JS with comments and code used only for static analysis makes a lot of sense too
for a community that doesn't have a strict types system for 25+ years, typescript users is so madly obsessed with types
with swc and rust-based dynamic compilation, typescript adds milliseconds of compilation time no one would notice.
It is kind of moot why they would do this. Unless they never plan on including swc or using deno for their tools.
Wow! I didn't know about this approach. Can I use trpc with jsdoc? Another way to put it, can I use typescript library with jsdoc like ts-pattern, effect-ts?
Very helpful post
Of course, it's a preference... but imagine someone migrating from Java or other typed language. It's better to learn typescript than javascript plus jsdocs.
For me, it looses the best of typescript. And also, we have infer typing that should be so complex to recreate using comments.
And also, the jsdocs is much more extensive to code.
Compilers are done to avoid it. Or do you planned to put on production your code with comments? no? then if you have any step between code and production, you have a "compiler"
Read the article again
Just like PHP! 😉
I do not understand the benefits of deliberately disguising the titles. If it were TypeScript^TM, then you either need a transpiler or a new JS implementation.
What you do have is an alternative type annotation system for javaScript, so why not name it honestly like that?
Once this is done, we can also honestly discuss about the differences, advantages and disadvantages compared to TypeScript. It is true that you can fully check the types with your special linter. It is also true that, e.g. the browser JS-engine can interpret it without compilation/transpilation. But honestly, if you write a project with more than 100 lines of code, is the loss of a fast transpilation step soo big that you want to get rid of it so desperately?
As someone experienced in using strongly typed languages, I can say that using type annotations first-hand makes you think about algorithms quite differently. The extreme is certainly Haskell where people are reluctant in writing documentation and instead spend the time on finding expressive names for functions, variables, types and typeclasses. The Haskell compiler may be slower than the babel transpiler, but usually the development time is not wasted during compilation, but on working out types after writing down your idea of an algorithm.
(Oh, and Java is not the archetype of a modern strongly typed language. Contrarily, Java is slowly picking up convenience from languages such as Haskell, Kotlin or TypeScript.)
If you write the ".d.ts" file before writing the implementation, then you may make things easier, but if you are to write the type specifications after the implementation, then you will have to think about the implementation twice. In the latter case I will predict that type annotations will always be handled as second-class citizens, i.e. only if and as much as you really must do them.
Wow. Lots of very opinionated comments here.
I came to say thank you: My team (who predominantly write in other languages, and struggle sometimes with certain aspects of JavaScript) have held me back from adopting TypeScript for good reason: it raises the bar for entry on codebases. That's not always a bad thing, but it is for us.
By using your methods I now have at least some valid way of checking types and it's already revealed some things that were missed in a recent refactor. So, thank you.
good
Thank you for your article! For me the switch from Javascript to Typescript has been painful, because it broke the better tooling I had for Javascript, forced us to switch off some useful eslint rules that were not supported for typescript, caused us to talk about which typescript features to use for months which displaced talking about how we want to structure our code (that has serious implications for a large codebase which we have to clean up), but most of all because it destroyed our chances to run without transpilation by using only standard Javascript.¹
I had voted for the JS+JSDoc approach, but back then the JSDoc tooling was still worse (weaker typescript support for it), we had strong proponents of TS (it’s strange for me to see that much zealotry outside ethical issues), and now we’re stuck with TS (which — to be fair — many prefer, especially those who only wrote Java before).
¹: I wrote down the complete lessons learned in Materialized Typescript Risks — one thing not in this article: Typescript failed to deliver on having fewer bugs.
This is a great writeup!
Besides: like the rest of my site, the article is copyleft (text is cc by-sa), so you can share it as you see fit as long as you link it as source and license what you make with it under cc by-sa, too.
Thank you! :-)
Great article!!! Thank you so much for showing us another paradigm, another way to think out of the box!
Definitively there is no silver bullet, neither perfect technique... but as engineers, we need to know all the options, and as responsible engineers, choose the right tool for the job!!
The dogmas doesn't help to grow! If we follow a truth, and became that truth in our only absolute truth in life... that truth becomes a lie, and we become fundamentalist!
Once again, thank you for you bravery to show us another way to think.
Thanks for writing the article and taking the time to deal with the inexplicably religious (in a bad way) comments. I've seen dev communities falling into dogma multiple times before (remember the TDD or Redux mobs anyone?) but the toxic intensity of some of the responses is still unfathomable for me.
Its mindblowing
Very inspiring post. Just tried out the jsdoc comments in my editor right now. The intellisense and type inference both work fine but it does not throw an error when I assign a string type to a number type.
Here is what I mean:
As you know, if I wrote the code above in typescript I would get an error right away, but it doesn't work when I do it in Javascript using jsdoc.
Anyway, thanks for the post. It was really good
You still have to configure your project to use typescript, or enable it globally in your IDE. There's a good post about setting up this way of working linked above in the post itself :)
I tried what I saw in the link and it works very well. Thanks alot😅.
Great :) I was just setting up this for you: github.com/thepassle/uh-oh-typescript
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more