DEV Community

Discussion on: Using Typescript without compilation

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.

Some comments have been hidden by the post's author - find out more