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!

Top comments (125)

Collapse
 
rafaelberaldo profile image
Rafael Beraldo

For those wondering how to typecheck js files, use this tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "checkJs": true,
    "allowJs": true,
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "types"
  },
  "include": ["src/**/*.js"]
}
Enter fullscreen mode Exit fullscreen mode

And you can generate types using npx tsc command (remember to npm i -D typescript as well).

Collapse
 
jesperordrup profile image
Jesper Ordrup

Two main reasons why I prefer jsdocs

1) total control of the final js
2) code is so much more readable

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

Code is more readable... the Jsdoc of complex types certainly not

Collapse
 
jesperordrup profile image
Jesper Ordrup

I'll put them in mycomplexytypes.d.ts

Collapse
 
westbrook profile image
Westbrook Johnson

In your travels, you wouldn't happen to have seen any tools that convert between TS and JS + JSDocs? Would be interesting if there were a "lint" step that would allow the preference to be a developer preference rather than a project preference or a code type preference.

I say "code type" as I've found in my projects that my for deployment/publication code feels "right" to be TS as I'm doing other conversions to that code anyways before making it available to others, but the project internal tooling needs to be "at the ready", and JS + JSDocs allows that to run exactly when I need it. Feels sort of why Deno chose to run TS files out of the box, without the TS version match inconsistencies.

It's almost like we're due for a revolution of "responsive coding" where there's enough AST info to allow the user to choose how their code displays as much as how their sites are dark vs light... 🤪

Collapse
 
thepassle profile image
Pascal Schilp

Not that I'm aware of, but its an interesting idea. What you're suggesting would sort of be a toggle between TS/JS+JSDoc, correct? Technically speaking you should have information in an AST to achieve this, but I think it'd be a fair bit of work to make it happen

Collapse
 
westbrook profile image
Westbrook Johnson

Yeah, likely. This would be like the number two thing that AST should be used for but no one has time for right after making tabs vs spaces be a developer preference, just the same.

Collapse
 
meduzen profile image
Mehdi M.

ChatGPT is good at doing this conversion.

Collapse
 
trulogik profile image
trulogik • Edited

This what you're looking for. At least, for turning Typescript into JS with JSDoc comments.

github.com/angular/tsickle

Collapse
 
thepassle profile image
Pascal Schilp

Cool!

Collapse
 
meduzen profile image
Mehdi M.

In addition to ESLint, and Svelte, there’s also all the open source work from @iskin (PostCSS, Nanostores, Logux…) using the same practice.

Bonus: you can test your types using tsd. See examples in one of my libraries:

Collapse
 
zirkelc profile image
Chris

That’s a very interesting topic, thank you for sharing. I personally prefer TS over JS in all cases, even for super simple things. I just like static types within my code and don’t perceive them as noise. They actually add context to the function and make it easier to grasp the full picture.

But I do understand that it’s a matter of taste, and if someones coming from a pure JS or other dynamic types language, it can feel like unnecessary noise and complexity.

JSDocs seem like a good compromise.

The discussion of ESLint rewrite in JS with JSDocs mentioned at the beginning of the article is also super informative. I’d like to encourage everyone to read it. It’s discussing pros and cons of both TS and JSDocs in the context of other big projects.

Collapse
 
seanmay profile image
Sean May • Edited

This can be a valid pattern.
It's certainly something that I do when I am too lazy to set up swc for personal projects.

But JSDoc does not have 100% support for TS features. It doesn't even have 100% support for type-checking in-editor. I cannot stress that part enough; know that you are getting yourself into the potential of more manual work, when TS features aren't supported, or when you need rigorous type checking, as that is the tradeoff for moving to JSDoc. Also, some of the types you end up writing inline become huge behemoths, if you hope for inference to carry you through the whole codebase.

If you are making a straightforward Java style app, or C style app, where virtually nothing is generic aside from arguments to built-ins like Arrays, or there just aren't any generics at all... and there is definitely no inference at all, then JSDoc can be pretty straightforward. It's not going to hold you to TS strict-mode rules, but it will at least document types for you.

But if you are doing more advanced type algebra, leaning hard on type inference and modern (/ ML-like) type system features, it is brutal to type some of the definitions, importing them from other JSDoc files turns into a fool’s errand, and even if you do get it all, the language server doesn't carry the context from file to file, to hold onto your chain of inference.

I can recommend it for either:

a. lazy personal projects that don't need the TS to be perfect

b. teams that were never using advanced type features, and aren't relying on those types in the doc being rigorously enforced (and doing it yourself; especially across files)

but not for more complex systems or systems with more advanced type requirements.

Collapse
 
jorenbroekema profile image
Joren Broekema

This is just wrong. It's straight up misinformation. I dont even know what to say. Give me a TS example and I will create you the equivalent in JS/.d.ts files with JSDocs typing and show you the type checking is exactly the same.

Collapse
 
seanmay profile image
Sean May

Sure. I have wackier examples, and more challenging examples, but a good start would be a 1:1 replacement for

codesandbox.io/s/nameless-fog-9z02...

It's a pretty self-contained, self-tested sandbox.

Oh, and you aren't really going to convince me unless all of those files are JSDoc, not TS.

And the reason for that isn't disingenuous, there is a very simple explanation. A bunch of more advanced algebraic TS relies on testing input types, and possibly generating intermediate types, either locally scoped, or otherwise, and as a space saving measure, things end up utilizing typeof some object / array declared as const. So either that type needs to be in the file with that const, or the type file needs to import JS which is backwards (and would lose context of the key/value pairs, reverting to name: string; instead of name: "Bob" as JS doesn't have an as const specifier, and I don't think freeze has been typed to preserve literal values, yet (as many built-in types working with immutable data haven't).

Looking forward to seeing the results, though I feel they will not be as terse.

Thread Thread
 
jorenbroekema profile image
Joren Broekema

You just gave me like 150-200 LOC of ONLY types. If your entire project is only types, you don't need JavaScript at all, and it wouldn't make sense to not just use TS. I have a bunch of repos where there's a lot of advanced types and I too put them in .TS or .D.TS files because the authoring experience is much better and from an authoring perspective you can't do everything with JSDocs that you can with TS. That said, once we get to the actual JS code that actually runs and does something in a browser or Node runtime, I can import the types into the JS files and be happy. You don't demo your types, you don't do browser testing or integration testing with them, they are just types... you may not even compile them, ever. I don't want to shit on your example, but I really think you are missing the point I've been trying to make about authoring types in TS/D.TS files while keeping your source code (NOT types) as JS files as to not be stuck compiling your code (not types) pre-test/lint/demo.

Thread Thread
 
seanmay profile image
Sean May

"can't do 100% of everything in JSDoc you can in TS"

That's not the OP’s position. OP’s position is that they are 100% swappable. If you are disagreeing with this point, then that's fine, I don't have a problem with your disagreement.

I also don't have to do any compiling of that code if I make a type error, the editor screams at me. My types are as tight as they are so that my editor will scream at me.

This example might be trite, but it doesn't stop the fact that if your goal is to make a library that does live API constraint validation, so that, provided an expected data type, it makes you write the correct validators to guarantee the input is correct, that the types which facilitate that are going to be really brutally painful written in JSDoc.

Nor if you are trying to get literally correct types of structs, when reading binary files.

There are literally TS features which are not supported when written in JSDoc comment format. The live server for in-editor type inference will literally lose the thread on recursive types or composed types faster in JSDoc. You might not be compiling code, but if you need to run tsc to check your types as you type, because the type hints and autocomplete are no longer functioning correctly at that particular depth (hence that exact code example), then it is not 100% replaceable. And if your team needs to do these things, or needs to import/export types from JSDoc files into other JSDoc files, because those types depend on as const and typeof, et cetera, to guarantee that your type and your dictionary/tree/etc are always in sync, then you will have an abjectly miserable time, relying on your editor to reflect those problems.

SWC these days is ~5 minutes of setup for dumping source to output, takes less than a second to run, and does large codebases in fractions of a second.

Thread Thread
 
thepassle profile image
Pascal Schilp

OP’s position is that they are 100% swappable.

Im not sure who you mean by OP, but if it's me, then this is wrong; this is not my position, and I urge you to read the blog again.

Also here are your types used in JSDoc

Image description

Thread Thread
 
jorenbroekema profile image
Joren Broekema

That's not the OP’s position

You need to read this blog again.

OP’s position is that they are 100% swappable

Not for writing your types, for writing your source code they are but some types will need to be written in .TS/.D.TS and imported in JSDocs in your JS source code.

The live server for in-editor type inference will literally lose the thread on recursive types or composed types faster in JSDoc

This is blatantly untrue. The exact same logic is used by TS language server, your editor will scream just as loud if you use JSDocs + JS file versus TS file. Your editor does not "lose the thread faster" or do worse at typehints and autocomplete. OP has proved this fact multiple times in the replies, but here's another example:

Image description

I used this example because it shows how to import from a .TS/.D.TS file and then consume those types in a JS file, and the editor is definitely screaming at me for not adhering to the types. It doesn't show it in the screenshot but the typehint/autocomplete works 100%.

So hopefully I've convinced you that you are wrong about the types not working as well, it is absolutely identical. However, I didn't need a compilation step to run my JS code, because it's JS and it runs without one.

SWC these days is ~5 minutes of setup

That's 5 minutes too many. And no, if you have testing, demoing, linting and you use custom tools for it, you first need a TS-loader for all of them, which will undeniably take longer. Great that you have a setup with all batteries included that works for all your projects, my projects require more custom setups which need to adjust often and I can't just take a Vite or SWC or whatever template and just assume that it'll be good enough for me.
SWC these days is ~5 minutes of setup"

Thread Thread
 
jorenbroekema profile image
Joren Broekema

Lol beat me to it

Collapse
 
thepassle profile image
Pascal Schilp

This is not true, gatekeep-y and bait.

Collapse
 
seanmay profile image
Sean May • Edited

There is literally nothing gatekeeper about it. I am a huge proponent of TS, and of ML-flavoured algebraic TS.

https://codesandbox.io/embed/nameless-fog-9z02xw?file=/src/index.ts&codemirror=1

If you can rewrite this using only JSDoc .JS files (no TypeScript definition files, for actual practical reasons), without changing the file/folder structure, I will tip my hat to you, because I have tried on more than one occasion.

I have literally been working on personal projects trying to create literal types of structs read from the Quake 1 binary files, with not only known number types, but known byte-lengths and element counts, through tagged typing. If you want to show me how that's a smoother experience in JSDoc than spending 15 minutes setting up electron, an SWC backend build, and SWC frontend build, a chokidar watch on asset files / client code, and a two-line preload script to hot-reload, so that the app can run either in electron or in browsers with WebGPU support... be my guest. It is a hard problem to solve once you get into data that doesn't align like modern data, and types which dynamically expand into different types as you continue reading the file data.

Thread Thread
 
andrewtrefethen profile image
AndrewTrefethen

Start by making a practical example. Then people might actually regard your input with some amount of weight.

You used 174 lines of TS to guarantee at compile time that 5 strings in your own source code had a particular value. You would have ironically made a stronger case if you had simply demonstrated usage of the TitleCase type itself. Being able to guarantee at compile time that the content of a string literal has a particular value is interesting, but irrelevant to daily usage. Those types give you no runtime guarantees. If your app is taking data from a user, api, or database, you will have to create or use a validator to make sure the incoming data meets those expectations. If typescript gave you the ability to automatically create and utilize said validators, that would be one thing. It does not. You can use code generation tools to automatically build these from your types, and deal with the cryptic runtime errors when a piece of data does not conform to your expectation. Or you can write a proper validator that directly outputs the desired type and throws discernable descriptive errors when it is not possible to do so.

Being able to say "these next fifteen bytes constitute this type, of which the first 4 are this other type, etc" is again interesting in principle, useless in practice. You will have to validate the data when it is read in anyway, and can tag the types as part of the return.

If instead you would like to use these as type guards on a function call or when you intend to write INTO a buffer, then you have moved the issue back further into your code. Something somewhere will have to validate that the data actually matches your expectations prior to you outputting it. There is some marginal utility if the data is wholly constructed within your code with essentially zero input from an outside source, but then you can constrain the types anywhere along the chain, not just the final function call.

The reason why it took you 174 lines to do in types what would take just 14 in a function, or 5 if you just use const variables and be done with it, is because you are misusing the type system itself to do something it can't actually do. It has no runtime checks, it cannot verify that the data you just grabbed from the database is actually in Title Case, it can't verify that those next 15 bytes represent what you think they do. You're example is an attempt to represent a compile-time parser / serializer which does not function.

const SlayerTitle = "Doom";
const PinkFloydTitle = "Several Species of Small Furry Animals Gathered Together in a Cave and Grooving with a Pict";
const StephenHawkingTitle = "A Brief History of Time";
const EricBogleTitle = "And the Band Played Waltzing Matilda";
const RedHotChiliPeppersTitle = "Under the Bridge";

Look at that, not only did it not require Typescript, it required zero type assertions at all since the const strings have automatically inferred types.

Begin by making a practical example, then someone might take you seriously.

Thread Thread
 
thepassle profile image
Pascal Schilp

I didnt advocate using only JSDoc. Im advocating using .d.ts files in combination with jsdoc.

Thread Thread
 
andrewtrefethen profile image
AndrewTrefethen

I'm unsure if you meant to reply to me. I was replying to Sean May on why their example code was ridiculous and not close to what you would use for practical code. That is why no one even took the time to attempt converting it into just jsdoc or jsdoc with .d.ts files.

I was telling him to use a practical example if he wished to have people take his critiques and challenges seriously.

All that said, 90% of the type alchemy he did is possible in inline jsdoc with realtime ts type checking. the few holes are easily patched by .d.ts files as you stated.

Thread Thread
 
thepassle profile image
Pascal Schilp

Ugh, sorry, the dev.to UI for replying to comments/threads gets a bit messy with so many comments and replies, so I think I screwed up somewhere.

 
seanmay profile image
Sean May

The example wasn't made for this purpose. The original example was made back when I was teaching a team how to use the (relatively new at the time) template literal types, to dynamically create keys from known (externally validated) input types, to a dictionary lookup, which were statically provable, and carried inference all the way through, once past the data service responsible for calling the API and returning a valid response. I coupled that with recursive types that were being used in a dynamic layout generator, based on input as an n-ary nested tree of known node types. This example, funny enough, solves for both template strings, and recursion. Go figure I would pull this out when claims that JSDoc is a good authoring / user experience for these types of things.

This is a self-contained, dirt-simple example to pull out, which has instant real-world application with only trivial changes.
One such example would be statically converting CSS to JS and back; another would be pascal-case to camel-case to serpent-case and back, to provide immediate developer feedback on errant style input. Yes, that requires a runtime test as well. What's your point? You think this can't be made useful with little more than an if/else?

The code that dynamically built the lookup keys and ensured proper access was preceded with what was essentially a precursor to Zod; a micro-library that when given a type as input, would force you to write a set of nested guards matching the shape of the object, which all rolled back up to one boolean, representing the conformity of the received input data, along with helper functions for composing those tests together, via boolean operations, and automating the casting of testing functions into type guards for the type that was inferred by traversing that far through the tree. This was now half a decade ago. It had a sister library intended to do the same thing, but respond with all validation errors sorted by the paths taken through the tree of data, rather than with a type guard representing the validity of the tree.

Would you have preferred that I updated and provided the code for that library? I mean, I can update it for a post 4.0 world, but then I really do expect to see 100% of everything rewritten in JSDoc.

If your app is taking data from a user, api, or database, you will have to create or use a validator to make sure the incoming data meets those expectations. If typescript gave you the ability to automatically create and utilize said validators, that would be one thing. It does not. You can use code generation tools to automatically build these from your types, and deal with the cryptic runtime errors when a piece of data does not conform to your expectation. Or you can write a proper validator that directly outputs the desired type and throws discernable descriptive errors when it is not possible to do so.

Why would you presume I'm unfamiliar with any of that. It is, in fact, why they were two separate libraries which could share the same composed test functions; one library was for quick validation to determine whether a data service could successfully parse data to then be transformed into an internal domain type... and the other library was for identifying causes of errors, at some point after the initial callstack was exhausted, so that string-building and error-reporting didn't eat cycles that should be reserved for either recovering or notifying the user / calling client.

Being able to guarantee at compile time that the content of a string literal has a particular value is interesting, but irrelevant to daily usage.

It really isn't. If it is an arbitrary string like in this example, sure. But aside from CSS to CSS-in-JS, another practical example comes from the DataView component which has buffer access methods like getUint8 and setFloat32.
JS has the Uint8Array constructor and the Float32Array constructor, whose name properties, funnily enough, are Uint8Array and Float32Array. You can see how a simple get${Arr.name.replace("Array", "")} would actually give you a valid method for each of the supported data types... but you should probably also see how TS would not be cool with you doing that in strict mode. I'm not saying that this is an every-day event. Nor am I saying that everyone on the team can / ought to be doing this type of problem solving. I am saying that for cases like these, writing these things in JSDoc is painful.

Being able to say "these next fifteen bytes constitute this type, of which the first 4 are this other type, etc" is again interesting in principle, useless in practice. You will have to validate the data when it is read in anyway, and can tag the types as part of the return.

It's not about automatically casting to a domain type, it's about being able to validate that the input matches the expected input, and thus can be cast to a domain type. And this is again, not a regular thing, but going through multiple layers of transforms from non-aligned, packed binary data from 1996, that loosely conforms to a spec, to data that can be worked on in modern JS, to data that can be loaded into a WebGPU compute shader, to data that gets bound to vertex / pixel shaders... having clearly defined domain types, as you move from layer to layer helps to know what you are working with at any given point in time.

The reason why it took you 174 lines to do in types what would take just 14 in a function, or 5 if you just use const variables and be done with it, is because you are misusing the type system itself to do something it can't actually do.

But it can do. It's Turing complete, the only reason it "can't" do, is because the Language Server is only bound to the code editor output. If there were bindings you could use to declaratively dictate what external code the Language Server should run, based on some command buffer spat out by a type, then the TS type system would essentially run like a Lisp that takes action on the top-level return. Again, not saying it should. But it can. If JS wasn't provided any bindings to browser objects, document objects, the terminal, the filesystem, et cetera, then it couldn't, either.

Thread Thread
 
andrewtrefethen profile image
AndrewTrefethen

You would have been better off making a smaller example that exercises the type system in ways you believe you're not going to be able to do in jsdoc.

The example you linked is pretty useless without the sister library you mentioned because except in EXTREMELY simple cases, typescript won't/ can't marshal a parameter of say String into the more restrictive types even with proper guards in place. I'm glad you were able to use a sister library to facilitate that, but that is the sort of additional detail that is crucial to an informed critique / analysis of an argument.

Okay, so the point is to demonstrate the template types and type recursion. That is doable in jsdoc today with the correct instrumentation. while codesandbox.io is missing some of the features necessary, desktop vscode is not. As proof, I have authored the following repository github.com/Trapfether/jsdoc-TS-equ.... Check it over yourself and verify that the types match yours exactly. No tricks, no hacks, no slight-of-hand necessary. Just a properly configured tool. JsDoc has been made nearly as powerful as typescript proper because the typescript team has put massive effort into doing just that. They along with the JsDoc, and visual studio code teams have patched the vast majority of the holes there used to be.

In the few instances where you literally cannot express a concept in jsdoc, then you can quickly author a .d.ts file and move on with your work.

There are legitimate reasons to pick typescript proper. There are concepts that exist in typescript CODE that don't exist in regular javascript because TS constructs additional functionality to facilitate such things. No-fuss enums being one particular example. However, those are reasons to use the typescript LANGUAGE and compile / transpile it to js. Typing is not one of those reasons any more as they have worked HARD to make jsdoc work nearly identically to typescript typings itself.

This is all to say that while you might find a niche use-case for compile-time string literal validation, you don't need typescript proper to do it. js + jsdoc with typescript checking your types will do a bang up job.

Thread Thread
 
seanmay profile image
Sean May • Edited

I wasn't looking for an "informed critique" of my code or the usefulness thereof, I chose this example for the express purpose of choosing something difficult to express and write and maintain using only JSDpc comment notation. Why? Because TypeScript is about types. Yes, I realize TypeScript has enums; it would be trivially simple to point that out. Also, enums in TS aren't great and come with a bunch of caveats. So have the decorators that have been around since Google agreed to use TS for Angular 2 instead of their "AtScript" with annotations that they demoed circa 2014. And?

More useful is a statically known recursive descent layout generator that takes an AST of nodes and their attributes, and a dictionary of generator functions, and showing that the whole thing can be statically known as you build, to prevent node types from being given incompatible children on the CMS side, or to recover from it on the client side.

Or a validation / marshalling library that does static validation and then marshalls to a domain type, rather than just a "trust me bro" cast.

If the VSCode team has put in the work so that this is 100% viable today, in JSDoc comments, then congrats to them. That is a big accomplishment, and I am glad Microsoft let them carry that out to completion. That's grwat. Mea culpa. That wasn't the case even 2 years ago, though. It would fail on asserts and revert to general types. And this post isn't "JSDoc has come so far from when the VSCode team started work on it, years ago" it's "consider moving all of your TS to JSDoc".

Again, this isn't about "how useful are these lines of code", but rather "how likely is the language server to choke on these types, and if so, does that signify that it is not ready to handle the more demanding cases". Because it isn't about publishing this code, it's about publishing medical devices, or financial applications or logistics devices with confidence that the developers aren't going to be allowed to make mistakes resulting from the type system going on vacation, or the dev just flippantly bypassing the types and doing what they are going to do, because "it's just JS".

Thread Thread
 
andrewtrefethen profile image
AndrewTrefethen

You posted a link to a code sandbox that spent 174 lines to do nothing but constrain the content of 5 constant variables to be a specific value. When it was pointed out to you that the usefulness of said code was less than questionable, you THEN mentioned the usage of an additional library that would facilitate marshaling a less-restricted datatype into the expected restricted type. You did not show off any of the useful demos you have since referenced in the original post. So you should not be surprised that several people (including myself) took one look and decided it literally was not worth our time. THAT is what is meant by informed critique / analysis. I used both critique and analysis for a reason, disjointing the two creates a different type of statement (something you should be particularly pedantic about considering the content of your comments).

Being able to describe in types the valid syntax of various recursive IDLs I can see the value of, especially when paired with a code gen that can take care turning your types into a parser and or validator for incoming data. Being able to describe in types recursive tree structures is also useful. You demonstrated neither in your original link containing comment.

Finally, I believe you said this in your earlier comment:
"If you can rewrite this using only JSDoc .JS files (no TypeScript definition files, for actual practical reasons), without changing the file/folder structure, I will tip my hat to you, because I have tried on more than one occasion."
I did just that. I'm waiting.

Thread Thread
 
thepassle profile image
Pascal Schilp

And this post isn't "JSDoc has come so far from when the VSCode team started work on it, years ago" it's "consider moving all of your TS to JSDoc".

This was never the point of this post.

Thread Thread
 
seanmay profile image
Sean May • Edited

When it was pointed out to you that the usefulness of said code was less than questionable, you THEN mentioned the usage of an additional library that would facilitate marshaling a less-restricted datatype into the expected restricted type.

I didn't, actually. I mentioned that this was a several year old code example, that was moved to this sandbox .. a while ago, and its intent was to demonstrate both recursive types and template literal types, because both were poorly understood by the group who required them. And it's not "constraining 5 strings", it's arbitrarily statically providing the correct (if simplified) title-casing for a myriad of input in plain English. That it demonstrates the point using 5 strings or 10 strings is inconsequential, and you could literally type in a title, copy and paste the type hint, and now have the correct casing (unless you expressly want Strunk and White, Chicago, or the like, which you could actually reconstitute by recomposing some of the higher-level types to appropriately meet the needs of the given style guide). I only mentioned additional other code, outside of this perfectly self-contained sandboxed example code, when it became abundantly clear that absolutely everyone is missing the point of me using exactly the type of code that would break in JSDoc inside of JSDoc, regardless of what other surrounding code there happened to be. See, normally, people want isolated usecases that effectively demonstrate the problem in a vacuum, and I have a number of premade examples.

This is valid TypeScript. It literally does not matter what the purpose is or how many lines of JS actually run. It is TypeScript, and this kind of aglebraic TypeScript does, indeed, have many practical purposes. This particular example, as previously stated, is not, itself, a practical purpose, despite using tools which, themselves, serve practical purposes. "Well, you should have made an entire IDL, an entire recursive descent generator, an entire tree of valid input data, and an entire map of node->layout transformers, and constrained the whole thing to 50 lines in the same file, when part of your concern is JS-to-JS imports of types, expressly for the purpose of demonstrating the issues you have" is not an ask I would expect to have levied if I went to, say the TS Language Server team and said: "Hey, I'm having issues with type recursion, here is some type recursion that is known to break". "Well it should be running more JS" has literally nothing to do with the problem I mentioned, and is a complete non-sequitur.

The quality of the code, the authoring style, et cetera, have not really been critiqued. The analysis of, say, runtime performance of the in-editor language server, or type correctness, has not really been provided. The only analysis that has been provided is lines-of-code and number of lines of running JS, and the only critique provided is "this problem is not worth my time". Which is fair, but if JSDoc is going to solve all of these problems, then perhaps actually demonstrating that would be helpful, especially when someone who previously had experience with earlier revisions of this tool, for this purpose, is providing examples known to break in earlier versions of the tool. Even just a blog post with a codeblock that has live type inference, a la the TypeScript sandbox editor, to teach these concepts, would be a valid usecase for this code, given that this is from a sandbox that was literally used as an advanced demonstration of the synthesis of these features, highlighting real-time editor feedback of the types. Unless your argument is literally that if it's not production code, it's inherently worthless, this is useful code within the context it was produced and used.

Demonstrating that JSDoc is now ready to handle advanced TS usecases, when it previously wasn't, doesn't only apply when the exact code-snippet provided is deemed "useful", for your particular definition of "usefulness".

If you would like examples which need to be further updated for the current versions of TypeScript, because they were written with ~3.7 support in mind, and before most existing codebases even had access to unknown:
github.com/seanmay/io-guard
have at it. There's another library that also could not be written in JSDoc even a couple of years ago, with expectations that it would function to spec, in editor. And the in-editor experience was the entire point of the library, by forcing you to write tests that conformed to the type and existence of the value, regardless of simplicity or complexity of said value. Does it have use? I don't know. That depends on the user. Someone using Joi and blindly casting on the other side probably thinks not. Someone using Zod these days probably thinks not. Someone who types JSON.parse(x) as any definitely thinks not. But I'm not asking whether the library is currently needed, nor worthy of VC funding. It served its purpose in a time when nobody else was providing solutions to the problem meaningfully. And so the point isn't "would you donate to this", the point is, "can JSDoc handle the esoteric nature of some of this, because before now, it really, really couldn't". Moreover, it isn't about answering "well, why would you do it this way when you could just use yup or a single giant function with typeof checks, or why don't you do it this way, instead" like... yeah, the point is not "can I write code the way you write code, and therefore conform to the exact style needed for your claims to be true" it's "can JSDoc do exactly what I need it to do, regardless of what that thing is". I can't believe I have had to say it this many times; people's lack of ability to extrapolate use out of intentionally self-contained, sandboxed examples is missing the forest for the one single tree of "but this exact example of 100% valid TS doesn't run JS code, so none of these concepts can possibly be valid or useful in any other way, and thus no JSDoc writer would ever want them to run in JSDoc" and is myopic at best. Curried functions which rely on type-inference of constrained generic types within the parameters of the returned partially applied function also had problems in JSDoc, but I am indeed terrified of the nature of the conversations had once I provide examples of those, beyond what is in the repo.

I did just that. I'm waiting.

Yes, you did. To the VSCode team, I say congratulations for fixing the myriad holes that prevented this from working, previously. And to you, congratulations for proving to me that my views on JSDoc's capability as an input source to the TS language server are outdated, and for doing what I could not do, all those times before. You even did it without impacting the code style too deeply, so kudos for that, as well.

Thread Thread
 
andrewtrefethen profile image
AndrewTrefethen • Edited

Here is your updated challenge github.com/Trapfether/io-guard-jsd....

The editor experience is as you would expect from typescript. Note, I did not bother with getting tests to run as that is unrelated to the challenge. You would have to change to jest-babel since jest does not support esm imports natively. The tests will fail with "SyntaxError: Cannot use import statement outside a module" despite obviously being modules.

This was completed as the last one, no .d.ts files, no hacks, no tricks. The OP never was of the position that you should avoid typescript files all together, he has repeatedly stated that using .d.ts files is a-okay in his book. I hope that my work here demonstrates to you that the need for even that is greatly diminished compared to even a few years ago.

The point being made about your original post is that by all accounts, until you expanded on why that code might be useful, it appeared to be an esoteric slapstick attempt at a gotcha. A waste of time. You can absolutely do things in TS that cannot even now be done is JSDoc in the same way. That by no way means it could not be accomplished in some other manner, or with a different tool, library, harness, etc. The original post looked much closer to a troll response needlessly contorting the type system to accomplish a ridiculous goal with substantially easier solutions. It was only AFTER you supplied additional context that reason for such contortion was made apparent. Had your original post done even a subset of the useful functions you outlined afterwards, this perception of being a troll never would have fallen on your post. Even a modest example of guaranteeing the content of the string was valid XML instead of a specific string, or as I said in one of my earlier responses, just typing the string such that is HAS to be title case. A slightly more complicated version would have forced the "A" article to follow the rest of the title as is common when features are displayed in a list. I'm sorry that I thought you were trolling.

None of this is to say you should / can abandon typescript. If you're using some of the features not available in regular javascript and they legitimately make your life better, more power to you. If you are shipping a consumer-facing app utilizing one of the major frameworks, then you are already bundling, tree-shaking and packing; throwing in a compilation or transpilation isn't going to really hurt things. If the ONLY reason you are using the typescript LANGUAGE and therefore undergoing a transpilation / compilation step is for robust type safety. That is no longer NECESSARY, there is now an extremely viable alternative. I hope this has been eye opening for you. Have a great day.

Collapse
 
emwadde profile image
emwadde
Collapse
 
seanmay profile image
Sean May

I’ve actually been excited for real work to be put into trying to flesh this out. One major concern is not stepping on the toes of potential future JS syntax, but regardless, having the ability to tag / annotate and have those annotations ignored at runtime would be all kinds of happy-making.

Thread Thread
 
arnebab profile image
Arne Babenhauserheide • Edited

It also sounds really dangerous, because shipping literal typescript to the browser gives Microsoft leverage against all browsers: they can add things to TS (that they control) which are only supported in edge (that they control) and other browsers have to follow or require a transpilation step that Edge does not need — pushing devs into using Edge.

No, I don’t trust them.

Though if my tooling were to update (once the proposal reaches stage 4), I might regain my better tooling that way. Which would reduce the pain of TS a lot.

Thread Thread
 
seanmay profile image
Sean May

But the spec is about ignoring type annotations, and doing so in a way that TS and Flow and Hegel and others would all just run in-browser, because all of the type systems would just be ignored.

I wouldn't put it past any browser manufacturer to go off-spec, but if they did that, they would be begging to have Flow files just take down thousands of websites in their browser, when they try to do some proprietary thing and explode on similar but marginally different syntax.

Thread Thread
 
arnebab profile image
Arne Babenhauserheide

"explode on similar" — that sounds like the third step: (1) Embrace, (2) Extend, (3) Extinguish.

But the other way round: provide some extensions that only work with their browser. Do you still remember the "works best with"-tags?

First marketing that as a pure devtooling thing (you can simply copy the code into the browser console of our browser) and once it’s supported in more browsers make it mandatory and add support for doing special stuff when in their browser. Finally changing the language spec so updating to the newest typescript only works on browsers that keep up. This enables them to force other browser vendors to play a constant game of catch-up, because MS controls Typescript, so they can add their plans early in Edge and make the design match the features while others have to retrofit them in a hurry to stay competitive.

Thread Thread
 
seanmay profile image
Sean May

Internet Explorer extinguished through monopolistic practices in marketing and availability, not through making the internet break. The breakage was because Microsoft would add new, nonstandard features or implement new nonstandard features in their own way.

It's not like Edge would release an update today that made all jQuery pages break. Do you think everyone would move to Edge in that case? No, they would stay on Chrome where they already are (and given that Edge is Chromium it would take a bunch of work for Microsoft to retake control of all of the stuff that they just relinquished control over a few years ago, like the JS runtime and the viewport renderer, specifically).

The spec would allow for a flooding of the internet with not only TS pages but also Flow pages. If all of React just died, because it still internally had published Flow annotations, do you think people would be flocking to Edge in that case? It would be a death sentence.

Edge currently has ~10% of the desktop and ~0% of the mobile market, I believe. How, exactly are they going to exert this monopolistic control, especially considering that it's a Chrome browser with a coat of paint and some usability features on top.

Thread Thread
 
arnebab profile image
Arne Babenhauserheide

Edge currently has ~10% of the desktop and ~0% of the mobile market

As you wrote, the codebase is the one of Chromium — Edge adds features on top and has a dev team that can change Chromium itself. That’s the market power, definitely not only 10%.

The breakage was because Microsoft would add new, nonstandard features or implement new nonstandard features in their own way.

Yes, exactly that. Imagine what would have happened if Microsoft had also controlled not just Frontpage but the dev-tooling used by 70% of web developers.

They don’t need to retake control of what exists, because they have control over what’s added.

And React does not have to die. It suffices that it’s just slower. Ahead-of-Time-Optimize for datastructure-definitions from Typescript types, but not from React. Or only allow usage of certain Edge-specific browser-APIs if typed with Typescript. Nudge people into Edge. Make React apps just somewhat less usable.

If you think that can’t happen, look at how part of this already happens with frameworks optimizing for Chromium, but not for Firefox. This is without having full control over the language used.

But then this is just what happened many times before. It’s not guaranteed to happen again.

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
 
jarrodhroberson profile image
Jarrod Roberson • Edited

I have to say, I learned something new in this article, that is rare for someone that studies their craft as much as I do for as long as I have (38+ yrs). This makes TypeScript actually useful. Everytime I have decided to try and use TS it has just been a complete mess of lots of overhead for very little benefit. I am going to look into doing this hybrid approach, thanks for posting this.

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
 
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
 
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
 
brense profile image
Info Comment hidden by post author - thread only accessible via permalink
Rense Bakker

We all already use typescript without compilation... Pretty much every modern dev env out there has support for serving typescript code without compiling it first... It's called transpilation: vitejs.dev/guide/features.html#typ... you only build/compile your typescript code when you deploy it to production and that compilation step is maybe 5% of the total build time. bundling and minifying takes much longer.

Collapse
 
thepassle profile image
Pascal Schilp

Not everbody uses vite. Also this still leads to some of the downsides I mention in my post, have you actually read it?

Collapse
 
brense profile image
Rense Bakker

Everything that uses SWC or Babel transpiles your Typescript rather than compile it. Vite is just one example of a build tool that uses SWC. I honestly can't think of any dev tooling that actually compiles your Typescript during development. I just wanted to clarify that speaking about compilation as being a downside of Typescript is a non-issue.

Thread Thread
 
thepassle profile image
Pascal Schilp

Except its not, the reasons for which are outlined in the post. Also not everyone uses vite or swc.

Thread Thread
 
brense profile image
Rense Bakker

Everytime anybody gives an example, you jump on it with the same repetitive argument that not everybody uses it... Do you have actual statistics for how many people do NOT use babel AND NOT use SWC?

Thread Thread
 
thepassle profile image
Pascal Schilp

Because frankly I'm tired of explaining, when people clearly aren't reading the post :-)

This will be my final comment: There are tools, like for example Esbuild (which vite uses internally) that strip the ts types from your .ts source code on the fly during development time. There are also other ways/tools to transform TS to make it feel like .ts runs natively in the browser/node environments. Which indeed some people find to be a nice developer experience, I'm not disputing this, I'm not saying that this is wrong. If you enjoy this kind of development workflow; good for you! However, this way of working still does not address some of the points I outlined in the article, which is why I keep referring back to it.

Thread Thread
 
brense profile image
Rense Bakker

Ok so you found a complicated way of working with Typescript, that makes it slow in development, which is why nobody uses that way in the first place and now you want to find a solution for that problem that doesnt exist?

Thread Thread
 
thepassle profile image
Pascal Schilp

its literally not complicated :')

Thread Thread
 
brense profile image
Rense Bakker

Yes your solution is less complicated than the complicated way in which you suggest people use typescript except... nobody is using typescript like that in the first place... I've been telling you this the whole time, you're solving a non-existing problem.

Thread Thread
 
thepassle profile image
Pascal Schilp

This is such a non-argument. As mentioned in the blogpost, which I doubt you've read, because you keep missing points that are addressed in the post itself, I very happily use this approach at work.

I'm gonna leave this discussion at this, I have some JSDoc comments to write :)

Thread Thread
 
brense profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
Info Comment hidden by post author - thread only accessible via permalink
Rense Bakker

You go on and on about your approach. I have not made one comment about your approach, the only thing I have been telling you this whole time is this: nobody is compiling typescript during development. Maybe you should read the title of your own article...

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
 
xerullian profile image
Patrick Nolen

Great article!

I don't mind compiling code, but it's nice to have an option; while still getting the benefits of typescript.

Collapse
 
lionelrowe profile image
lionel-rowe

Some of these environments, like Deno, support running TypeScript natively (which has a whole other set of worrisome implications).

What are those worrisome implications? The only one I can think of is the potential performance hit, but Deno now skips type checking by default, so any performance hit in practice is negligible with default settings.

Collapse
 
thepassle profile image
Pascal Schilp

The issue with this is that typescript doesn't follow semver and allows breaking changes on minor/patch versions. This could mean that a package could break at any time when Deno decides to upgrade typescript. The TS versions is defined by the runtime, not by yourself/your dependencies

Collapse
 
lionelrowe profile image
lionel-rowe

@thepassle that's also handled (at least in theory, and I'm not aware of any real-world counterexamples) by defaulting to no typechecking.

Per Deno FAQs:

We do not consider changes in behavior or breaking changes in TypeScript releases as breaking changes for Deno. TypeScript is a generally mature language and breaking changes in TypeScript are almost always "good things" making code more sound, and it is best that we all keep our code sound. If there is a blocking change in the version of TypeScript and it isn't suitable to use an older release of Deno until the problem can be resolved, then you should be able to use --no-check to skip type checking all together.

In addition you can utilize @ts-ignore to ignore a specific error in code that you control. You can also replace whole dependencies, using import maps, for situations where a dependency of a dependency isn't being maintained or has some sort of breaking change you want to bypass while waiting for it to be updated.

I guess that's already a little out-of-date, as --no-check is now default and you instead have to pass --check to opt in.

Thread Thread
 
thepassle profile image
Pascal Schilp • Edited

I wasnt aware that no check was the default now, which indeed mitigates that issue, thanks for pointing this out!

I've edited the post to reflect this :)

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