DEV Community

Pascal Schilp
Pascal Schilp

Posted on

Using Typescript without compilation

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:

A simple  raw `add` endraw  function is created, when typing  raw `/**` endraw  the code editor autocompletes the scaffolding for the types, and documentation is added to the function

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
}
Enter fullscreen mode Exit fullscreen mode

And import it in your source code:
./my-function.js:

/**
 * @typedef {import('./types').User} User
 */

/**
 * @param {User}
 */
function foo(user) {}
Enter fullscreen mode Exit fullscreen mode

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 return type of the  raw `add` endraw  function is being inferred correctly

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
 */
Enter fullscreen mode Exit fullscreen mode

example B:

export interface User {
  username: string,
  age: number
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
astrojuanlu profile image
Juan Luis Cano Rodríguez

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!

Collapse
 
zakius profile image
zakius

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

Collapse
 
redstone576 profile image
RedStone576

for a community that doesn't have a strict types system for 25+ years, typescript users is so madly obsessed with types

Collapse
 
tamusjroyce profile image
tamusjroyce

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.

Collapse
 
shadowgg profile image
shadow

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?

Collapse
 
rexsteuber95776 profile image
Rex Steuber

Very helpful post

Collapse
 
joaozitopolo profile image
Joao Polo

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"

Collapse
 
thepassle profile image
Pascal Schilp

Read the article again

Collapse
 
chargoy profile image
Isaac Chargoy Vivaldo

Just like PHP! 😉

Collapse
 
melli79 profile image
M. Gr.

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.

Collapse
 
endymion1818 profile image
Ben Read

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.

Collapse
 
asawriter profile image
asawriter

good

Collapse
 
arnebab profile image
Arne Babenhauserheide • Edited

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.

Collapse
 
thepassle profile image
Pascal Schilp

This is a great writeup!

Collapse
 
arnebab profile image
Arne Babenhauserheide

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.

Collapse
 
arnebab profile image
Arne Babenhauserheide

Thank you! :-)

Collapse
 
mkvillalobos profile image
Manrike Villalobos Báez

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.

Collapse
 
isaachagoel profile image
Isaac Hagoel

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.

Collapse
 
thepassle profile image
Pascal Schilp

Its mindblowing

Collapse
 
michthebrandofficial profile image
michTheBrandofficial

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:

/**
* @param {number} a
*/
function returnNumber(a) {
   return a + 2;
} 

returnNumber('a');
Enter fullscreen mode Exit fullscreen mode

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

Collapse
 
thepassle profile image
Pascal Schilp

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 :)

Image description

Collapse
 
michthebrandofficial profile image
michTheBrandofficial

I tried what I saw in the link and it works very well. Thanks alot😅.

Thread Thread
 
thepassle profile image
Pascal Schilp

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