[HN Gopher] TypeScript is terrible for library developers ___________________________________________________________________ TypeScript is terrible for library developers Author : qudat Score : 177 points Date : 2022-08-23 18:31 UTC (4 hours ago) (HTM) web link (erock.prose.sh) (TXT) w3m dump (erock.prose.sh) | finder83 wrote: | Like many here, I love typescript for library writing, at least | internally. I've not made any big npm packages, so not sure how | that would do, but I imagine I'd like it a lot too. | | What I hate is getting the build system set up to generate the | right combination of .js/.d.ts files to work with the build | system of whatever I'm using it in, particularly for libraries | that use any form of preact/tsx files. I must be seriously | missing something, but after 5 years as a node/react developer I | still suck at making build pipelines for new projects. | franciscop wrote: | The only convincing reason to use TS I've seen so far as a | library author, which TBF is actually not about TS but about | TSDoc/JSDoc, is how the autocomplete in VS Code works. Going from | an empty/barebones autocomplete to actually having the | documentation in-line is a pretty nice extra feature for my users | IMHO: | | https://twitter.com/FPresencia/status/1545932895126175746 | brundolf wrote: | I find that most of the pain/complexity around TypeScript types | happens when you're trying to write super dynamic or polymorphic | JavaScript-style code and then put types on top. This comes out | especially when adding types to an existing codebase. I can | definitely see how this would affect library authors more than | application authors, but I'm not sure it's TypeScript's fault, | and I think it can be avoided by making different choices about | what interfaces you expose | | Or, worst-case, just weakening your types. Plenty of libraries | have weaker types than they could have, but sometimes it's | justified because it would be hard to get them exactly right | Hirrolot wrote: | I have a similar essay entitled "Why Static Languages Suffer From | Complexity" [1]. I don't deal with TypeScript, so it's up to you | whether the essay relates to it or not. Basically, the idea is | that languages make type-level and value-level programming | different in surface, while they're (or at least can be) the | same. From hence we have type-level astronautics that boggles | library developers' minds. | | [1] https://hirrolot.github.io/posts/why-static-languages- | suffer... | nwsm wrote: | The article boils down to asking for the TS team for better | documentation and tooling. Hard to argue with that I guess. | | > In effect, we are shifting complexity from end-developers to | library developers. | | Is this not why libraries are written in the first place? | vosper wrote: | Something I've long wanted is a type-explainer - something that | will let me highlight parts of a type definition and tell me in | English what the fragment means. The example that OP linked | elsewhere in this thread [0] is a good one - I know what parts of | that mean, but I've never seen "T extends string = string" | before. | | [0] https://news.ycombinator.com/item?id=32569708 | Smaug123 wrote: | It's just "T extends string", with a default value of `string` | if you don't supply T. | [deleted] | throwaway247322 wrote: | That would be cool to see, actually reminds me of the cdecl | explain project [1]. | | [1] https://cdecl.org/ | kansface wrote: | | How do library developers debug their highly dynamic and heavy | use of conditional types, overloads? | | Don't write that code because it's impossible to debug and test? | If typescript yields pain here, it's appropriately felt imo. | zethraeus wrote: | If you write your library in typescript, not JS, do you still | have any of these problems? | | If not, it sounds like one core issue is 'adding types to an | existing untyped codebase is hard'. This checks out. :) | no_way wrote: | Writing new library and expecting people to actually use it | without providing types is not realistic. You can write library | in JS, but you will still need to provide types, since that's | what ecosystem is right now. | wizofaus wrote: | I disagree it's hard per se, but I'd agree it's hard to do | well. Especially if you have a bunch of JS functions that | return (or expect) objects with very similar but slightly | different properties. Figuring out the overlap and the semantic | relationship between the types is often challenging, because | the original authors never thought about the problem in terms | of what types of objects they were dealing with. | lucideer wrote: | Typescript is bad for library developers who have produced bad[0] | APIs and don't want[1] to refactor them. | | Overloads exist in Typescript because as a language it needs to | support what javascript supports - regardless of whether the | pattern is "good". Overloads are heavily used in DefinitelyTyped | because contributors there haven't had the luxury of designing | the APIs they're typing. If you're designing your own API, you | shouldn't be heavily using overloads because typed languages | don't encourage the ridiculous trend of movable arguments that | jQuery pioneered in the JS world. | | Similarly, if you're writing all of your own code you shouldn't | need type manipulation because you shouldn't be leaning heavily | on variable mutation. Dogmatic stances on immutability are | reasonably a matter of debate, but it's fairly uncontroversial to | state that minimising mutation in your data flows helps prevent | your code becoming an unmaintainable mess. | | Etc. | | [0]Writing good APIs is hard - bad APIs are inevitable. Typed | languages make refactoring faster. | | [1] Refactoring untyped languages is very hard and the tendency | not to is deeply ingrained and very natural/reasonable. | ajkjk wrote: | This, 100%. In this very comment section there are a bunch of | people saying "I maintain <API with crazy unnecessary | complexity in it> and I can't figure out how to translate it to | TS". Well yeah, that API sucks, make a simple one instead. | dangarbri3 wrote: | I think valid points are bad about typescript being bad, like | without a real debugger it's going to be hard to debug anything. | | But I firmly disagree with how the article begins. | | > In effect, we are shifting complexity from end-developers to | library developers. | | That is the whole point in creating a library. To hide complexity | in a nice easy to use interface/API. | johnfn wrote: | > That is the whole point in creating a library. To hide | complexity in a nice easy to use interface/API. | | Totally agree. Minor ergonomic changes in a library have a | wildly disproportionate impact on developers. If I save a | developer 5 seconds with a better type, and I have 1000 | developers using my library, that's 5,000 seconds I just saved | - and that's assuming they only ever use my type a single time! | With this in mind, I'm totally OK paying a tax on making | libraries a bit harder to write, if it means that the benefits | of stronger types fan out. After all, writing that type could | take 4,995 seconds for me to write and still be a net positive. | | And honestly, 5 seconds isn't even close to accurate. Good type | definitions have saved me hours. | Smaug123 wrote: | Yeah, the author appears to be complaining that Typescript is | forcing them to stay honest even while they perform Mad | Shenanigans like dynamically constructing types :/ if TS didn't | check it, it would be down to your users to report the bugs in | production! | franciscop wrote: | I don't think it's very fair. As a library developer myself, | you try to make you user lives easier, which implies being | flexible in what you accept when possible. A couple of | examples I struggled with recently: | | Documenting https://umbrellajs.com/documentation#addclass. | The way I documented it is by opening with a code snippet | with many of the possible options (which can also be | combined!): .addClass('name1') | .addClass('name1 name2 nameN') | .addClass('name1,name2,nameN') .addClass('name1', | 'name2', 'nameN') .addClass(['name1', 'name2', | 'nameN']) .addClass(['name1', 'name2'], ['name3'], | ['nameN']) .addClass(function(node, i){ return | 'name1'; }) .addClass(function(){ return 'name1'; }, | function(){ return 'name2'; }) | | This is pretty easy to do in plain JS, and of course if you | are writing code and using it you just read the first 1-4 | lines and know what to do for 99% of the cases, while also | noticing there's few "advanced/flexible" ways of using it. | How would you even do that in TS? | | Then there's a classic initializer in JS that works like | this: function myLibrary(arg) { | if (!(this instanceof myLibrary)) { return new | myLibrary(arg); } ... } | | This is very useful to create a library like jquery that you | can initialize straight away without needing (but also being | able to use) the `new` keyword, just calling it like a | function and always ensures it returns an instance. To this | day I haven't found a way of doing this in TS. | josephg wrote: | You can list variants of a function's signature in | typescript, but typescript won't help you much with | "stringly typed" things (like | `.addClass('name1,name2,nameN')`). | | Different languages have a grain like wood does. And that | subtly directs you by making some things ergonomic and some | things difficult to express. I love typescript, but I | definitely find it changes the resulting code. | | Typescript makes "jQuery style" javascript much more | awkward to write, because its harder to type. This is good | and bad. I write less scrappy code in typescript - which I | think makes it a worse language for quick prototyping. But | the tradeoff is that I think its a better language for | larger teams / longer lasting projects where functions are | read a lot more than they're written. | | The actual typescript answer for your API is "don't make | your API look like that". Its not always the answer you're | looking for. | overgard wrote: | Honestly, that's just silly. There's absolutely no reason | to accept that many different call styles. Why not just | take in an array? As a user I don't find things like this | convenient, I find them to be confusing footguns. It's a | one liner to split your comma separated string into an | array as an end-user, but once you add that complexity to | the interface in the library you can never ever take it | out. | LtWorf wrote: | I thought in the js world it was normal to break | compatibility whenever. | LelouBil wrote: | A lot of times making the user's life easier is about | having a single correct way to do something. | Smaug123 wrote: | There's "be flexible in what you accept", and then | there's... "take a comma-separated string which you could | parse into your arguments" :) | robbiejs wrote: | I agree. I am the creator of the data table lib | datagridxl.com and I like to make my methods as flexible as | possible. Example: | | grid.selectRows(2) // index grid.selectRows([3,5]) // range | grid.selectRows([[1,2],[4,6]]) // multiple ranges | | It fits in the JavaScript spirit of "we will make it work" | which I love. | | Other major thing that made me decide to develop in es6 | instead of typescript was compilation times. After a ctrl+s | it had to compile ts to js for 10 seconds, which is | annoying for me, as i like to check & test every minor code | change. | int_19h wrote: | > It fits in the JavaScript spirit of "we will make it | work" which I love. | | Do you also use == and != for comparisons by default? | HideousKojima wrote: | >To this day I haven't found a way of doing this in TS. | | Just make a static method that does initialization if | needed and returns a new instance? | | https://www.typescripttutorial.net/typescript- | tutorial/types... | | I'm trying to not be too hard on people as I read this | thread, but it's baffling to me that web devs are getting | filtered by features that have been in other languages | since the 80's and 90's. | phailhaus wrote: | declare function addClass(...classes: (string | string[] | | (() => string))[]): void; | | It's pretty straightforward in Typescript. And when you go | to implement it, tsc will make sure you cover all the types | your function claims to support. | | > This is very useful to create a library like jquery that | you can initialize straight away without needing (but also | being able to use) the `new` keyword, just calling it like | a function and always ensures it returns an instance. | | Avoiding having to type "new" is not a very compelling | reason to avoid Typescript, especially because Typescript | won't let you make the mistake of calling the function | without it. It's just not a problem. | franciscop wrote: | That's an order of magnitude less clear in what the | function expects than the examples I gave IMHO | phailhaus wrote: | Who's stopping you from giving examples in a docstring? | [deleted] | darepublic wrote: | the type definition of the same is clearer imo and not only | that it is enforced by both the ide and the compiler. | Libraries typically DO NOT write a bunch of code examples | of all the legitimate arguments that can be passed. Also in | your example above, > .addClass(function(node, i) {return | 'name1'}) ^ what is node? What is i? Seems intuitive to | think it must be a dom reference and an index. But in | different domains it's not always gonna be so clear. Like I | am not familiar with umbrella js, but maybe node could be a | jquery object and not a plain dom ref? With typescript you | can just say | | type GetClassName = (node: HTMLElement, i: index) => string | | string[] // see how I added string[]. So I don't have to | add yet another example // of a function returning a string | array instead of a string | | and then add it to the union of types that can be passed | into addClass. Great, no more guessing based on the domain | knowledge I have (or don't), it's crystal clear. And it | forces the lib developer to have the discipline to make it | crystal clear, which they usually don't I'm afraid. | bzxcvbn wrote: | Your problem would be solved pretty easily by making two | changes to your API: | | * Only accept an array of strings. Not a single string, not | several strings, not several arrays of strings, and | certainly not a space/comma-separated list of classes. | | * Add another single overload where you accept a function | that takes two parameters. That function can ignore its | parameters if it wants to, and it returns a list of | strings, so you don't need to accept several. | | You have the same functionality, it's not harder to use for | an end-user, and it's infinitely simpler to type. | wvenable wrote: | Yeah I kind of disagree that "being flexible in what you | accept when possible" is a benefit to the user. It's just | more complexity pushed down to the user that is | unnecessary. | | In this example, I'm not sure why it's the functions | responsibility to support all of these options when the | user is perfectly capable of manipulating strings and | arrays. | jbm wrote: | While I agree with you, I have seen practical cases where | sensing an array of items in the get parameter of a web | server is handled differently, similarly to what the | parent comment mentioned. | tengbretson wrote: | If TypeScript steers authors away from either of these | patterns then all the better in my opinion. | qudat wrote: | Thanks for reading the article! | | > That is the whole point in creating a library. To hide | complexity in a nice easy to use interface/API. | | I think that's a fair point, but generating types that satisfy | all use cases is very challenging to get right -- | disproportionately so. I could see a world where -- without | proper tooling and growing complexity -- typescript libraries | becomes so difficult to maintain that people give up or burn | out. Maybe that's a pessimistic outlook but I already feel that | way some days. | Chabsff wrote: | In strongly-typed programming languages, which includes | Typescript, figuring out the types *of the interface* is not | something that's done after the fact. `@types/*` is an | exceptional project meant to back-port JS libraries to | TypeScript, but that's the exception, not the rule. | | If you write a library in TypeScript, determining what types | are present as part of the interface is one of the very first | thing that should be done. | mrkeen wrote: | > In strongly-typed programming languages, which includes | Typescript, figuring out the types is not something that's | done after the fact. | | Too broad a statement. There's loads of value in having a | compiler figure out types for you after/when you write the | code. | Chabsff wrote: | Hard disagree as far as the portions of it that are part | of the user-facing public interface are concerned. | | But granted, as a general rule you are correct. I was | referring specifically to API interfaces. | overgard wrote: | If you're talking about type-inference, sure, I guess | it's fine. | | If you're talking about figuring out what types you're | going to accept, you should absolutely be defining that | on your own up-front. If you don't even know what your | types are how is an end user going to figure it out? | heisenbit wrote: | Often libraries are bootstrapped from application code. Having | a step function in complexity is not helpful in fostering an | ecosystem with a broad range of maturity. | nichochar wrote: | >In effect, we are shifting complexity from end-developers to | library developers. This places a huge burden on us to be experts | with how typescript works. | | Interesting, I had never thought of this tension, but I think I | disagree with the author and think this is a good tradeoff. We | shouldn't assume all developers are library developers, most | beginner developers for example don't write libraries or | shouldn't. I think it's actually good to force competence in the | library author domain (within reason), since they can abstract | away this pesky complexity (the point of any good library). | | I like this new mental model, but I still agree with the author | that typescript is too hard to work with and developer experience | with it has a ways to go. | acemarke wrote: | Heh, it's amusing to see Redux Toolkit referenced here. I'm one | of the two main RTK maintainers. My co-maintainer Lenz Weber is | responsible for most of our TS type wizardry. | | Agreed that writing TS types for libs can be a pain. I actually | did a talk recently on "Lessons Learned Maintaining TS Libraries" | [0], where I talked about some of the techniques we used, and | some possible TS changes that would be helpful for us as | maintainers. | | As one recent example, TS made a change in a 4.8 pre-alpha that | broke RTK's `createSlice` types. Lenz tried to come up with a | fix, couldn't, and had to add a workaround to check what TS | version is being used and specifically use an altered type. Since | there _isn't_ a good way to know what TS version is being used, | Lenz resorted to hacking together a new package that abuses the | `typesVersions` property to define a different TS type for | _every_ TS major+minor version combo, and then used that to | decide what the RTK type should look like conditionally [1]. | | Another pain point is debugging type transformations. I reworked | the Reselect types in 4.1.x to do a much better job of inferring | the argument types for the final selector, based on the | intersection of all the input selector arguments. This ended up | as a monstrous type that does a types-level map + transpose + | intersection [2]. It took me weeks to get this working right, and | I frequently had to break it down into multiple small | intermediate types to see how TS was processing each step. | | I know that someone on Twitter was recently working on an | alternate TS type-checker based on bytecode, and they said they | had some kind of a working types-level debugger [3]. Having | something like that officially, where I could see each step of | how TS was transforming the types, would be _hugely_ valuable. | | There's a couple folks like AndaristRake who are able to dig into | the internals of the TS compiler itself to trace how it's | interpreting the types. I definitely don't have that ability :) | | [0] https://blog.isquaredsoftware.com/2022/05/presentations- | ts-l... | | [1] https://github.com/reduxjs/redux-toolkit/pull/2547 | | [2] | https://github.com/reduxjs/reselect/blob/v4.1.5/src/types.ts... | | [3] https://twitter.com/MarcJSchmidt/status/1539787500788613120 | helsontaveras18 wrote: | I agree wholeheartedly that debugging types is very difficult. | The best we have is hovering our mouse over the definition to | see what type was created from the definition. Obviously not | great. | | Also, debugging type differences with deeply nested objects | (like what happens with graphql schemas) can be hugely painful. | You need to copy the error message to its own file (since the | errors can be huge) and debug what specific piece failed. | | I do feel the Typescript documentation is lacking and the only | way to get better is to read open source projects to see how | others have done it... which is only helpful when they've | solved the exact problem you're looking for. | | It'd be great to have a recipe book of common advanced | typescript manipulations and how to compose them together. | azangru wrote: | Terrible as opposed/compared to what? | | Maybe a language with better type guarantees would be better | suited for library developers; but if the alternative is plain | javascript, then no. Just no. | darepublic wrote: | > Types add a lot of code to your library. When first attempting | to contribute to a project, one must grok the application logic | as well as the type logic. | | To me types are very welcome. It's much easier to grok a function | when I can see a type interface for it. Some libs do this better | than others. Some libs have types as an after thought and you get | confronted with something like: | | ``` export type Parameters = {[key: string]: any} ``` | | which is basically a giant FU and is as helpful as no type. But | to me, well maintained type api is a great boon to both | contributors and end developers. | andrewallbright wrote: | I think the author is good with naming symptoms and I think they | miss the true culprit. It's not typescript that is causing this | pain, no it's the pain of getting correct abstractions laid down | in code. It's truly the pain of code design activity. Typescript | and library programming is just forcing that pain to the surface | instead of hiding it away. | | I know this because I struggle against these "pain during code | design" & "get abstractions right" forces when I program. | | Declaring types is really declaring what domain objects your | program cares about and function signatures are really just | describing interactions that can happen. So what 'domain objects' | should meaningfully exist in my program and how do they interact? | A bad abstraction can burn up a lot of time, so make sure you | check your abstraction. I typically check mine describing stories | to my friends (or rubber duck) and ensuring I'm using plain | English and the other person doesn't get lost. YMMV | | When you're doing web client programming you're typically using a | library to accomplish work, and much of that work is reacting to | user input and rendering data. I would argue that because so much | of the linguistic heavy lifting has been done for web client | programming, practicing the abstraction exercise is done less | than in other programming domains. Doesn't mean it couldn't, just | trying to highlight a difference in programmer domains. This is | why the pain is more apparent in library programming than web | client programming. | | I imagine that advanced individuals in code design understand | more of the connection between math and programming and are | capable of describing systems of interesting work in few simple | statements. I hope to reach those heights someday. | chadlavi wrote: | As someone who started our inner-source react component lib with | typescript, it's actually really great | ummonk wrote: | > There are a lot of reasons why typescript sucks for library | developers, but at the end of the day it reduces developer | productivity. In effect, we are shifting complexity from end- | developers to library developers. | | Isn't that the whole point of libraries? Instead of having a | hundred end-users trying to understand a library's untyped API, | we save overall developer time by having just the one library | developer type its API. | 734129837261 wrote: | TypeScript has come a long way, but one of its biggest issues is | the complete lack of FORCED sensible naming conventions. | | Generics are the worst offender, with people often using a single | letter to represent something that's both hard to keep track of | and impossible to intuitively make sense of. | | Any type should be: `TUser` and not `User`. | | Any generic should be: `<GPerson>` and not `<P>`. | | Any interface should simply not exist because it's TypeScript, | not InterfaceScript. The addition of classes into JavaScript was | unnecessary sugar and they should just do away with it. So, | `type` for everything, no more `interface`. | | But that's a whole different subject. | | I've also noticed that people over-engineer TypeScript. I had a | team mate telling me he wanted to make URLs type-safe. I asked: | "But why?" and he had no answer. There was no problem preceding | it. There wasn't anyone asking for it. It wouldn't solve | something we didn't know. It would just make the codebase more | confusing to deal with because, in his solution, there wasn't a | single place where routes were defined. Nope, routes would be | inferred from Pages and their use of Page props. | | It was genius. But it also was over 2000 (two THOUSAND) lines of | code that I didn't feel like I wanted to deal with. | | I gave him permission to publish it and get the open-source | community involved. Once it's tried & tested over the course of | many users and lots of time, we could consider it. | | He wasn't happy. He was also not very pragmatic. TypeScript seems | to do that to people. The "never any `any`"-crowd is so tiresome. | erulabs wrote: | > It was genius. But it also was [...] code that I didn't feel | like I wanted to deal with | | You sound like a sysadmin my friend! This is exactly how I feel | about _plenty_ of software that I 've allowed into production, | but that now keeps me up at night and distracts me from | spending time with my kids. | | One of the most useful things about plain JavaScript at this | point is seeing if it causes genuine disgust. It's a razor for | idealism versus pragmatism. | schwartzworld wrote: | > The addition of classes into JavaScript was unnecessary sugar | and they should just do away with it. | | Weird. Classes are one of my favorite features, even before | using TS. I do a lot of data modeling using functional | techniques and the class keyword allows a really intuitive way | of encapsulating things. | yashap wrote: | Another way to phrase this is "statically typed languages suck if | you want to write highly dynamically typed code." That's true. I | think there's 3 options here: | | 1. Keep writing code in a very dynamically typed style, despite | choosing a statically typed language. Just deal with/stomach the | extreme type complexity that is necessary to model your dynamic | style statically | | 2. Keep writing code in a very dynamically typed style, and | switch to a dynamically typed language | | 3. Stop writing code in a very dynamically typed style. If you're | using a statically typed language, write code that embraces | static types | | IMO 2 and 3 are both reasonable choices, but the author is | deciding to choose 1, which is of course painful. I strongly | disagree that libraries must be super dynamic though - that's a | pure style choice that some library authors adopt, but you can | absolutely write basically any library in a static types friendly | style, you just need to reflect that in your interfaces. | | I guess the one thing that _IS_ TS specific is that ppl can write | their libs in JS, then try to add types to it, and if they chose | crazy dynamic interfaces, it will be an incredible pain to | statically type. But honestly, you could just say "this is a JS | lib, there will be no TS types." | [deleted] | cellis wrote: | I just wrote and published a library and CLI (for migrating | postgres databases) to NPM using Typescript ( | https://npmjs.com/package/nomadic ). | | The Typescript itself, coupled with jest TDD made development | very pleasant. The hardest part for me was figuring out how to | publish the types so when a user installs it using yarn install, | they get Intellisense. I think I did a poor job of it, as in the | end I just published the source code. Even though I prefer when | npm libraries do this ( as I can inspect the source in my editor | easily ), I know the pros don't do that. If anyone wants to take | a look and knows how to solve this type publishing, I'd be very | appreciative. | acemarke wrote: | Glancing at it very quickly via Unpkg, I _think_ you really | just need to fix the broken `types` path reference in | `package.json`. | | Right now, your `dist` folder has `dist/index.d.ts` available, | but `package.json` points to: "types": | "./lib/index.ts" | | Just change that to: "types": | "./dist/index.d.ts" | Veuxdo wrote: | "testing your types" | | If you find yourself thinking you need to test your types, you've | taken a wrong turn somewhere. | Hirrolot wrote: | Does it mean that languages should prohibit type-level | programming (or type-valued functions), like in Idris? | Smaug123 wrote: | In Idris, you've _typed_ your types. Much easier to deal | with. | tannerlinsley wrote: | TLDR: I write and maintain several good-sized typescript | libraries so I write library types all the time. In fact, I'm | writing them right now! TypeScript is what makes them really fun | to use. They infer everything they can and have very high levels | of safety and passthrough while still allowing for composition | and extension at the framework-adapter and library level. I will | never consider writing another OSS tool in the JS ecosystem | without ensuring the typescript experience is the best I can | offer. | | That said... | | I've learned that "library" types might not be the best way to | talk about these concepts. What we're really talking about here | are types that are complex/advanced enough to force you to | venture beyond the primitive building blocs in the TS docs and | into existing open source solutions that have trail-blazed more | advanced use-cases. It took me only a few weeks to get | comfortable with common TS, but at least a year to feel dangerous | enough to write more advanced TS. I'm on year 3 now and I am | still learning/forgetting so much about it. | | I agree that: - Advanced types and their concepts are difficult | to learn. - There is limited documentation on how to create/use | them. - They can sometimes be difficult to reason about, mainly | due to the limited syntax TS offers around advanced concepts. | | On the the positive: - They can be learned with practice. - There | is plenty of OSS out there to learn from - Once you learn them, | you start to think differently about TS as a language instead of | annotations | | I wish there were better features/syntax support for: - Optional | generics - Higher-order generics / Polymorphic generics | (basically higher-order functions, but for types) - More built- | ins (like ts-toolbelt, type-fest, etc) | | I think this ramp of difficulty with advanced TS types is fine | for the most part. Library authors have always carried way more | of a burden than devs at the edge, even during runtime to ensure | things like size, performance, flexibility, etc. TS is just | another facet that is becoming more an expectation every day. | | At the end of the day, a library dev gets to choose what level of | investment they'll put into great types for their library. If | they can pull it off, their tool will likely provide a measurably | better developer experience. | | That's my goal for my libraries, so choosing to go all-in on | advanced TS is now a no-brainer. | | Anywho, good luck! | jbirer wrote: | (Hoping to not invalidate anyone's concerns with this comment) | | The majority of complaints I heard on Typescript were in the | lines of: "I should be able to do this X thing that would not fly | in a static language / not a good idea". Many times Typescript | caught me doing stupid things like passing the wrong type or not | checking data adequately, also forcing me to comment my code by | describing shape of the data. It was a life saver for debugging, | though it's not perfect, it's more like lipstick on a pig for a | badly designed language. | harrisonjackson wrote: | I don't depend on the actual typescript docs much but thankfully | in @types and in tons of repos there are examples of well written | typescript code. | | The amount of JS and TS out there is also a bit of a foot gun | though so stick with heavily used/starred libs if you aren't | sure. | | One tool that helps a lot with developing libraries in typescript | is TSDX[0] or its successor dts-cli[1] and there is a bunch of | good stuff in awesesome-typescript[2]. | | Maybe library devving is harder?(more work?) with tyepscript but | it is worth it for the end developer, especially if that end | developer is you. If you aren't using your own libs then you're | probably getting paid by someone else to make them or... idk. | | [0] https://github.com/jaredpalmer/tsdx | | [1] https://github.com/weiran-zsd/dts-cli | | [2] https://github.com/dzharii/awesome-typescript#libraries | lucideer wrote: | > _Why are there no guides on the typescript site about library | developers? What about a guide on the recommended tools for a | library developer?_ | | I have no idea what this means. What are "tools for a library | developer"? | | > _I think the underlying assumption here is that there is no | difference between a library developer and an end-developer, | which I reject._ | | Expansion on why they reject this would be of interest here. I'm | a library developer - I'm not aware of any differences. | yesimahuman wrote: | This hasn't been my experience, certainly not in 2022 with all | the progress typescript tooling has made. Testing _is_ annoying | to set up the first time but once you have it working you barely | ever have to mess with it. | | As a library author myself I struggled a lot more with monorepo | management and changelogs/publishing but tools like | turbo/nx/changesets have really helped here. | paulddraper wrote: | > Why are there no guides on the typescript site about library | developers? What about a guide on the recommended tools for a | library developer? | | One interesting thing about Typescript is that it prefers .ts to | .d.ts files. | | So if you ship you .js, .js.map, .ts, and .d.ts files next to | each other, your downstream consumers will be running expensive | type inference on the .ts. | | Instead, you have to manipulate package.json entries so that the | .ts and the .d.ts are separate. (Or inline sources into source | maps and exclude .ts from the package.) | rowanG077 wrote: | I don't have written much TS but the author's experience is | exactly what I felt while writing TS. The bolted on "gradual" | type system makes typing harden then even dependently typed | languages I have used. I honestly believe TS has done | irrecoverable damage to the perception of types to millions of | web developers. | throwaway247322 wrote: | > testing types | | Right, this happens in any language where you are writing type | lambdas or functions at type level, which makes sense actually. | We've been doing this in C++ as well for years... | | On another note, Redux is fundamentally a bad experience in | typescript because the action type must extend from `AnyAction`, | so it poisons the well. | | What we need is a first-class typescript state container with a | rigid API, even better would be first-class enums and pattern | matching... | | Writing generators isn't fun in typescript, so yes I imagine | redux-saga would be painful. | acemarke wrote: | Note that our official Redux Toolkit package, linked in the | article, has complex TS types specifically _because_ we work to | simplify the TS usage experience for our users. | | Here's all you need to do as a typical Redux app developer to | get started using Redux Toolkit with TypeScript: | | - https://redux.js.org/tutorials/typescript-quick-start | | Note that we specifically tell you to use a | `PayloadAction<MyPayloadHere>` type, which results in well- | typed reducers and automatically generates strongly-typed | action creators to match. | | If you're using Redux at all, you _should_ be using Redux | Toolkit to write your Redux logic. | mcluck wrote: | I could not disagree more. I absolutely love using TypeScript for | the libraries that I write and maintain. | | > There's great documentation and blogs for end-developers but | there's very little for library-developers | | Because there isn't a difference. I write my applications the | same way that I write libraries. TypeScript gives you the | flexibility to do that as well. If you want to use looser types | in your apps and in your libraries, go right ahead. Some people | may want more accurate types coming from your library. They'll | either improve them or pick something else. You aren't required | to appease them. I know that you stated that you reject this | notion but just now I was working on a relatively straightforward | React app and I've got a number of places where I'm doing | slightly more advanced types. If you aren't, you're probably | leaning on library developers to do that for you which is fine | but that's a choice. | | > How do library developers debug their highly dynamic and heavy | use of conditional types, overloads? | | I literally just make a number of assignments and hover over the | types to verify that they look right. There are better ways of | testing these that I'll get to later. // | Hovering over this will either show what I want or not | type ComplexFoo = ToComplexType<Foo> | | Sometimes those types might look nasty and be difficult to parse | so there are ways to force TS to simplify them for inspection. | There are even libraries[1] that help with that. | | > I spend a decent amount of time in the redux world so redux- | toolkit is a great library to see how types are done | | Redux is an inherently very dynamic system. It's dynamic by | design. Capturing all of that dynamism in a strict system is | _hard._ `redux-toolkit` and related codebases are not | representative of the normal struggles for library developers. | Additionally, see point 1 about how you can choose how good your | types are. It would be technically correct for `createAction` to | just return interface Action { type: | string payload: unknown } | | They choose to make their types better so that developers don't | have to carry that burden. | | > It's pretty common in style guides to never nest ternaries. In | typescript, that's the only way to narrow types based on other | types | | I'm always open to syntactic improvements. If someone finds a way | to make the type syntax simpler without conflicting with existing | JS syntax then I will be very happy. That being said, the syntax | isn't so inscrutable as to be unusable. It just takes some | getting used to. | | > It's not enough to test your types against the latest version | of the typescript compiler either, you also need to test against | previous versions | | I disagree. The TypeScript team does a very good job of keeping | things backwards compatible. In the rare case that they don't, | your library is welcome to state that they only support certain | versions of TypeScript. This is a pretty standard problem with | any sort of dependency. Nothing about TypeScript makes this | particularly difficult. If you are going the extra mile to check | multiple versions of TypeScript then you're awesome. | | > This new class of tests are in their infancy and there's a | baron wasteland of tools that are now deprecated or partially | maintained | | My opinion: a lot of these tools become unmaintained because they | aren't necessary. Testing types is pretty easy. Include these two | functions and you're golden. function | generateType<TReturn>(): TReturn { return null as | unknown as TReturn } function | assertType<TExpected>(value: TExpected) {} | | Then testing your types is as simple as | assertType<Expected>(generateType<ComplexThing<number>>) | | > When first attempting to contribute to a project, one must grok | the application logic as well as the type logic. | | If you're running in to this then those people are doing it | wrong. Types should make impossible states impossible and do | their best to make the application logic the only thing that is | possible. If you understand the application logic, you understand | the type logic. They're one and the same. | | > I shouldn't have to read the typescript compiler source code in | order to figure out why it's resolving a piece of my code to a | specific type. | | You shouldn't and you don't. Better documentation is always | better though and I agree that more in-depth first-party | documentation would be great. | | [1]: https://millsp.github.io/ts- | toolbelt/modules/any_compute.htm... | robocat wrote: | Strong typing is most useful when used for library code. Users of | libraries can make their code as dynamic as they like. | | Edit: I think their complaints apply to library development using | any language with types e.g. complex C++ template foo is most | useful for libraries? There is little in the article that is | specifically a problem with TypeScript. | | * Good autocompletion. Great for developers using a library. | Somewhat self-documenting. | | * Narrows down origin of errors. Great for library users and | library developers. Nobody wants uncertainty or finger pointing. | | * Typing is a strong API contract. Breaking changes to the API | are more likely to break existing code. That is absolutely good | from the library user's point-of-view. | | If a library developer wishes to deploy a highly dynamic API for | their library, they can just use "any", but expect most | developers to dislike the library. | | Planning your types correctly, and designing the API carefully to | use types, does cost library developers a lot of time and | thought. But the library users get outsized gains because library | users usually hugely outnumber library developers. | | > we are shifting complexity from end-developers to library | developers. This places a huge burden on us to be experts with | how typescript works. | | That is to be expected. Good library code is hard to write. It | would be lovely if there were better resources to help library | writers, however it is a specialist role since library users | usually strongly outnumber library writers. | | It appears to me that many of the design decisions in TypeScript | for the type system are there to help library developers. | Patterns of usage were recognised and then added to the type | system. | | Disclaimer: not a TypeScript library developer. | benjismith wrote: | I liked the article, and I'm sympathetic to the overall point... | | > The kind of hoops I have to jump through to get types "just | right" in a web app versus a library is dramatically different. | It's rare in a web app for me to need constructs like conditional | types, type operators, and overloads. As a library developer, | they are heavily used. These constructs are highly dynamic and | embed logic into your types. This leads into my next frustration | with typescript which is debugging. | | I would have like to see some examples of "conditional types, | type operators, and overloads" in action, and an argument for why | these constructs are so much more prevalent in library code than | in application code. | | I don't have a counter-argument, and I don't have any reason to | doubt the author's insight, but after reading the article I don't | feel like I have an increased understanding of the problem-space. | usrusr wrote: | It's been ages since my last dive into the js galaxy, but from | my outside perspective I read that as "the usual mess that we | like to do to provide awesome backwards compatibility when | introducing wildly changed API generations is very difficult to | sneak past the typechecker". Not sure that I read it correctly, | but it would certainly fit "conditional types, type operators, | and overloads". | | Why are they more prevalent in library code: to make changes | non-breaking (or breaking, but fixable with minor tweaks). Of | course types can also be a solution to this problem, by | promoting consequences of incompatible change from runtime to | compile time, but that only holds if you can assume that all | calling code is type checked. | [deleted] | overgard wrote: | You only really need super complicated types to represent | existing very-dynamic javascript libraries. If you're starting | from scratch typescript is as easy and/or easier than javascript. | If you do have incredibly complicated types, it might be a sign | that you have a bad design. At work most of the typescript | libraries I wrote just ended up using interfaces and enumerated | strings and occasionally a union type. Sometimes I'd use "never" | to prevent certain things, but I can't say the types ever had to | get much more complex than that. | eyelidlessness wrote: | The problem with the premise of the article is that it | presupposes that a library's interfaces will, and ultimately | must, be complex. The great thing about TypeScript for library | development, _if you start with types_ , is that it strongly | encourages you to create simpler and less flexible interfaces. | That's not to say you don't sometimes need (or won't sometimes | choose) to use some of TypeScript's more complex features. But a | lot of the time you won't, or should think seriously about | whether your interfaces are becoming too complex. | | Much of the complexity of TypeScript's type system is a direct | result of excessively dynamic interfaces in existing, untyped | JavaScript. Starting with types is a good way to catch that | early, rethink the interface, and either isolate it or avoid it | entirely. | matsemann wrote: | It was bad the first few years, with existing code being hard | to type because it was creatively written. But now that almost | everything is somewhat typed, JS has converged to a way of | writing code, and you should consider the tradeoffs before | choosing a different path than the current norm. | fullstackchris wrote: | I really _wish_ this were true, but go look at a vareity of | "modern" React projects. React is so flexible that I've seen | nightmare implementations of what could be reduced to just a | few short lines. Then people turn around and blame React as a | 'garbage framework'. Then if you talk about a much more | verbose and strict framework like Angular, people turn around | and complain "I can't do what I want with this overly | opinionated framework!" | | Damned if you do, damned if you don't. | | However, if you look behind the curtains of these infamous | gripes (some of them so often repeated they are cliche), 99% | of the time its people who just haven't spent enough time | around a certain framework or language and go create a rant | post (similar to OPs post) | | You're free to love the tools and languages you use all the | time. But it's bad practice to go around bad mouthing tools | and frameworks you don't full understand or haven't used | productively. | david422 wrote: | We were trying to add types to some of our code. And the types | were getting ridiculously convoluted. And at that point it was | like - maybe it's not the types that are the problem, maybe | it's the interface that needs to be rewritten. | srcreigh wrote: | Care to share more details about code smell patterns you | noticed? | emn13 wrote: | The OP's example of createActions smells like such a | scenario. The way redux mashes up payloads and discriminators | makes it hard to type correctly - as in objects are not | simply composed, they're extended by adding properties, and | for no immediately obvious reason (which might be because of | my inexperience, granted). | | Had redux been developed in typescript from the start, I | doubt it would have chosen this API. Then again, the current | version is already in typescript, so perhaps that optimism is | unwarranted; unfortunately my experience with it is years ago | already. | acemarke wrote: | This particular use case is trying to strongly type the | "Flux Standard Action" object shape. An object _may_ have a | `payload` field for its data, _may_ have an `error` field | that indicates this action represents an error, and _may_ | have a `meta` field that provides additional descriptive | information. | | This isn't part of the Redux core package, but it's a | convention the community adopted shortly after Redux became | popular. Redux Toolkit uses that as the standard structure | for an action object. `createAction` defaults to just | `{type, payload}`, but you can optionally provide a | callback that adds the other `meta` and `error` fields. So | yes, there's some additional complexity here because of the | optional field contents, and this type is telling TS what | the final type should look like based on which fields | exist. | | Note that as an end user, you normally don't even call | `createAction` yourself - it's automatically called as part | of our `createSlice` API. What end users normally write is: | export const counterSlice = createSlice({ name: | 'counter', // `createSlice` will infer the state | type from the `initialState` argument initialState, | reducers: { increment: state => { | state.value += 1 }, // Use the | PayloadAction type to declare the contents of | `action.payload` incrementByAmount: (state, | action: PayloadAction<number>) => { // | `action.payload` is now a `number` state.value | += action.payload } } }) | throwaway0asd wrote: | Agreed! From the article: | | _How do library developers debug their highly dynamic and | heavy use of conditional types, overloads?_ | | Don't do that. It dramatically increases compile time and | results in a maintenance nightmare. Keep types primitive. | | In my own code I use _type_ unions of primitives and /or named | types. For objects I use interfaces. With that I am able to | provides types for about 98% of my code. That left over 2% is | generally for extending the global Array type or extending an | event. | acemarke wrote: | I'll agree, and disagree. | | I made a similar comment in 2019 about "TS pushes you towards | simpler APIs" [0], so I agree that it's both a good thing in | principle, and something TS usage leans towards in practice. | | At the same time... JS _is_ a very dynamic language, library | design reflects that, and library code by its very nature must | account for the different ways that users will want to call it. | That causes library types to be much more complex than app | types. | | [0] https://blog.isquaredsoftware.com/2019/11/blogged-answers- | le... | msie wrote: | Yikes! Why bend over so much for clients of the library? | bwestergard wrote: | "JS _is_ a very dynamic language, library design reflects | that" | | I would argue it should not. If your API cannot be well | expressed in the Typescript type system, it is your API that | should change. | [deleted] | hmsimha wrote: | This is all well and good until you have to deal with other | code and other APIs in your library (results from network | requests, localstorage, untyped javascript libraries, etc.) | cogman10 wrote: | Yup. | | Especially since types are documentation. If you are using | some meta dynamic type garbage, then I as a user of the API | am left to wonder "Ok, this takes <U, K extends keyof U>... | WTF is it expecting?" | | This isn't to say that sort of thing is ALWAYS wrong, but | rather it should be the exception and not the rule. | | You do types because constraints make code clearer. By | having a BF compiler in the middle of your template | definition, you've defeated the types and you might as well | put an `any` there with docs that say "here there be | dragons!" | naet wrote: | I'd rather not warp my whole project around a typing system | if I don't need to. I also might not be the owner of every | API I need to consume... | | I think TS is great in certain cases- I love being able to | get intellisense info from TS. When I download a new | library and I get those helpful hints built right in from | the libraries TS adoption, I love it. Typescript in this | instance is reducing friction in my development workflow. | | In my day job I work at an agency where we rapidly deliver | a lot of new web properties. I have used Typescript on | projects when it was a client requirement, but in our | environment using Typescript generally feels like it | overall adds friction and time to the project. Some team | member inevitably gets stuck spending a bunch of extra time | working with TS stuff: setting up the project with proper | rules, writing some complex interface to deal with some | random API we are either consuming or creating ourselves | (in which case it is usually under rapid prototype | development and needs to be updated constantly), etc. | | I'm sure it is very helpful when working at scale with tons | of developers on a highly stable and established project, | but in my opinion it isn't generally applicable to every | web property that it would be better on Typescript; it | really depends on the scale of the project and the scale of | the team maintaining it. | folkrav wrote: | > JS _is_ a very dynamic language, library design reflects | that | | This is mostly an issue if you're tacking on those types on | top of your existing design, rather than starting your design | from your types, no? | overgard wrote: | I don't think allowing users to call libraries in all manner | of crazy ways is a good thing at all, and I don't think | anyone would really be that put out by following a more | pythonic approach of just allowing one way to do something. | mmmpop wrote: | Yeah but you're writing libraries in Javascript, that ship | has sailed already. You're gonna just need a bit fat switch | statement to check your input types on every public | function you ever write. | overgard wrote: | It's never too late to stop making bad designs. I've | written plenty of (internal) libraries and if someone | asked me to put in a big fat switch statement I'd ask | them why they need it. | edgyquant wrote: | We're talking about typescript here | 323 wrote: | Python is also very dynamic, yet libraries rarely allow | arguments to be number/string/list/dict/function at the same | time. | | Yet in JavaScript I see this all the time - pass a string | URL, but we also accept a function which will return a string | URL, or maybe an option object - { url: string, strict: | boolean } | | So I think it's more of a culture thing. | nicoburns wrote: | Luckily these types are _not_ complex at all to type in | Typescript. You just enumerate all the options with a | | character separating them. | 323 wrote: | Right, but people compose. They take this union and put | it in another object, which also allows multiple types | for some field, and then they take that object and put it | in another one, and then you have a 10 line type | declaration for 3 fields. | nerdponx wrote: | Python libraries kind of used to do this more, but seem to | be doing it less now, in part because highly-polymorphic | interfaces are a huge ugly pain in the butt with the Python | type hinting system. | int_19h wrote: | What's so painful about writing foo: int | | str | nerdponx wrote: | It gets a lot more complicated than that. One of many, | many examples: https://github.com/pandas-dev/pandas- | stubs/blob/v1.4.3.22082... | | In general, if someone with experience in a domain is | complaining that something is hard, it's safe to assume | that the thing is actually at least kind of hard, and | that they aren't just a crybaby about indentation and | formatting. | overgard wrote: | I think allowing people to call functions in a vast variety | of ways is a mistake. It's one of the things that frustrates | me the most about the javascript ecosystem, it's hard to | figure out the call signature for any given function because | there's like 15 different ways to call it. I'd argue it's | better to just have one well documented way. It's not even | like there aren't patterns for it, ie passing an "options" | object or something like that (although even then I think | that can be an antipattern unless there truly needs to be a | variety of options -- ie a db connection or something. Better | just to have multiple functions with specific purposes | instead of one mega function that does everything) | cogman10 wrote: | Agree, 100%. Make your function definition `foo(foo: Foo)` | not `foo<T extends comparable | int | Bar>(foo: T)` | Flexibility is the enemy of a type system and library | design in general. | danielvaughn wrote: | Case in point, over the weekend I tried creating an npm | package for a custom hook. Since it's public-facing, I wanted | to use TS so I could expose types for users. | | Part of this hook is that users can pass in custom data. I | tried to create a type that was basically "an object of any | string key, with the type value". Gave up after like an hour | of banging my head against my desk. Maybe I'm new to TS, but | FFS it should _not_ be that hard to do. | jeremyjacob wrote: | Fortunately that's not too difficult: let | x: {[key: string]: Type} | | But maybe the documentation is lacking here? | aylmao wrote: | For the record, this is equivalent to: | let x: Record<string, Type> | | Pun intended. | [deleted] | refactor_master wrote: | I agree, and it's the same with Python. If you start with | static, type-safe first, then Python's flexibility ends up | being so horribly convoluted that you avoid it in favor of | simpler constructs. **kwargs? Massive union types? Super | flexible dicts? Rather not! | | I'll define 50 strongly typed constructs over having to run my | code through 50 times to catch all the runtime bugs. | rocqua wrote: | I have found *kwargs useful for wrapper functions, where you | pass kwargs to some other, later defined, function. | dimmke wrote: | Yeah, Redux Saga is known for heavily using JS generators and I | wonder how much of this is just the relative obscurity of that | language feature for him. It's not exactly a straightforward | library. | dfabulich wrote: | Here's a perfect example. I maintain a simple Node library | designed to connect to Apple's App Store Connect API. | https://github.com/dfabulich/node-app-store-connect-api | | It accepts, as a parameter, a URL for Apple's REST API. My | library handles authentication, and returns the parsed JSON | result, with a handful of tweaks to make the API more usable in | JavaScript. | | Depending on which URL you request, you'll get different result | object back. You could get a single object in response, or an | array of objects, and the _type_ of returned objects is | different for each URL type. | | How would you add TypeScript types to this API? Well, Apple | provides an OpenAPI documentation of all of their URLs, which I | could use to autogenerate types, but then, how would I handle | all of those types in response to the user's _string_ input? | | Well, it turns out that TypeScript is so amazingly fancy that | you can write _very_ clever code to parse strings at _compile | time_ , extracting parameter types etc. from string literal | types. https://lihautan.com/extract-parameters-type-from- | string-lit... | | The documentation explains how an API like this: | app.get('/purchase/[shopid]/[itemid]/args/[...args]') | | can parse its parameters into a Request type with shopid, | itemid, and args[] array parameters. This would catch a bug if | you had a typo, e.g. "itmid". | | But the code to do that looks like this: type | IsParameter<Part> = Part extends `[${infer ParamName}]` ? | ParamName : never; type FilteredParts<Path> = Path | extends `${infer PartA}/${infer PartB}` ? | IsParameter<PartA> | FilteredParts<PartB> : | IsParameter<Path>; type ParamValue<Key> = Key extends | `...${infer Anything}` ? string[] : number; type | RemovePrefixDots<Key> = Key extends `...${infer Name}` ? Name : | Key; type Params<Path> = { [Key in | FilteredParts<Path> as RemovePrefixDots<Key>]: ParamValue<Key>; | }; type CallbackFn<Path> = (req: { params: Params<Path> | }) => void; function get<Path extends | string>(path: Path, callback: CallbackFn<Path>) { | // TODO: implement } | | Nifty, eh? But, as the article says: how would you test this | code? How would you debug it? | | Clearly, I wouldn't do that. Instead, I'd write a script to | autogenerate individual methods, e.g. instead of | get(`apps/${appId}`) that returns a parsed JSON blob, I'd | autogenerate a getApp(appId) method that returns an App object. | | But that API isn't any _simpler_ than the API I already have; | it 's just different. | | And let's not forget that I'd have to write a script to | _autogenerate_ these methods (or just their types) based on | Apple 's OpenAPI specification, and now I have to _maintain_ | that code, updating my @types /node-app-store-connect-api | definition every time Apple introduces a new URL you can | request. Testing and debugging _that_ is a challenge in its own | right. | | And even if I did it, the complexity of my library just went | from a few hundred lines of glue code to 1,000+ lines of type | generation (plus tests for the generated types). | | This is in no way worth it for me. As a library developer, | adding TypeScript types would make my life harder. | biorach wrote: | > And even if I did it, the complexity of my library just | went from a few hundred lines of glue code to 1,000+ lines of | type generation (plus tests for the generated types). | | I would say that the increased complexity of your project | would accurately reflect the complexity of the underlying API | | > As a library developer, adding TypeScript types would make | my life harder. | | Yes, but it would mean that the various parameters passed to | your library would now be type safe, which sounds worth the | work | dfabulich wrote: | One clear moral of this story is that, as a library | developer, I need a lot more documentation and tooling | support from the TypeScript team. | | That string-parsing thing isn't in TypeScript's | documentation at all; it's a random blog post. I'm lucky I | found it. TypeScript's site should document how to do this. | | Furthermore, it should be easier to debug parsed stringly- | typed APIs using the `infer` keyword. Today, I just run the | compiler over and over again until it stops throwing | errors, and the errors are really not very helpful. | | There should be a standard tool to help me test that I used | `infer` correctly, ideally helping me by fuzzing the code | to see if I allowed something I shouldn't have allowed, or | blocked something that I should have allowed, and to test | against various versions of TypeScript. | | Narrowing types shouldn't be done just by nesting ternary | expressions. As the article notes, "It's pretty common in | style guides to never nest ternaries. In typescript, that's | the only way to narrow types based on other types. It's a | mess!" | | Lastly, this idea doesn't sit right with me: | | > I would say that the increased complexity of your project | would accurately reflect the complexity of the underlying | API | | I designed my library so it doesn't need to incorporate | that complexity. Users of my API have to learn the API, but | they can learn it from Apple, by reading Apple's | documentation, and by inspecting the objects that Apple | actually returns (rather than what their OpenAPI | specification _says_ they 'll return). | | Incorporating that complexity into my library will make my | library harder to work with, more difficult for others to | contribute to my library. As the article says, "Types make | it much harder to maintain a js library, and especially | difficult to contribute to them." | | TypeScript is forcing me to duplicate the API's complexity | in my code. Even if you think that "sounds worth the work," | I think you have to agree that the work load is much higher | than the work I've already done. | overgard wrote: | > Depending on which URL you request, you'll get different | result object back. | | This sounds awful to me. Why wouldn't it be separate | functions? I absolutely would not want that at all as an end | user, I'd find it confusing and frustrating. | dfabulich wrote: | Because in order to have separate functions, I'd have to | define separate functions for every function that Apple | supports, i.e. I'd have to autogenerate the list from | Apple's OpenAPI specification. | | Furthermore, if Apple introduces a new function, I'd have | to release a new version of my library to support it. | Today, my library can handle any object Apple's API | supports, without upgrading. | | As it stands, the API works like this. It's fine. | const {data: app} = await read('apps/123456') const | {data: apps} = await read('apps') const {data: | [firstApp]} = await read('apps?limit=1') | overgard wrote: | Swagger can generate that sort of thing. I guess I don't | know what things your library provides so I don't want to | bash it, but having a generic read/get that doesn't | provide types doesn't seem like a big improvement over | just using a general purpose tool like axios or fetch. | ricardobeat wrote: | Agree 100%. Redux et al are terrible examples where complexity | is through the roof, these libraries were written way before | TypeScript was the norm. You can eliminate almost all of that | by having simpler interfaces, but that requires a different API | built with types in mind. | mckravchyk wrote: | I agree that there's much more type wrangling when developing a | library vs. typing regular app code. I don't mind it all though. | rikroots wrote: | My personal experience as a library developer, who has written my | library in JS, not TS ... | | TS is an excellent choice for a lib dev starting a new project | today. I can see the advantages of using TS for the library code | - in particular for a library that gets popular and welcomes | contributions from other developers. However TS is a nightmare | for someone like me who: 1. started writing the library 9 years | ago; 2. has let the library get "quite" big; and 3. has only | learned to use TS in the past year (for the day job) and is | nowhere near to becoming a types expert. | | I've had experience of people suggesting I rewrite the library in | TS. Sometimes those suggestions have been quite 'evangelical' in | their tone. As an (essentially) solo developer I just don't have | the time, capacity or willingness to do that work - however much | the end results might please others. | | I also understand that having type definitions file for the | library's interface is, nowadays, a critical factor if the lib | dev wants others to use the library in their projects. But | writing a .d.ts file for a large, mature repo to at least help | those potential users can quickly turn into a World of Hurt. I | know this because I've done that work[1] and I never want to do | it again. | | As much as I know that TS is a Force for Good in the JS coding | world, there are days when I detest it! | | [1] - link to the Scrawl-canvas .d.ts file on GitHub - | https://github.com/KaliedaRik/Scrawl-canvas/blob/master/sour... | simonsarris wrote: | I am really surprised by this guy's opinion. I make GoJS | (https://gojs.net/), a diagramming library written in TypeScript | with 68k weekly downloads on NPM. The project began in 2011 and | we converted it to TS in 2018. It's been a _huge_ plus. The sole | downside was the initial time it took during conversion, but even | in doing so we caught bugs with incorrect input types, | documentation mistakes, bad range enforcement, etc. | | On our end, it enforces type safety better than the Google | Closure Compiler. There has scarcely been a problem with type | complexity that was not ultimately our fault. Just a couple minor | things that TS amended later. For that matter the TS experience | has only gotten better, generally. | | On our users end, we can now give them a .d.ts file that's much | richer and easier for us to produce to aid their autocompletion. | And we can use that .d.ts file to ensure that all the methods we | intended to expose/minify are getting exposed. The advantages | with the .d.ts and documentation make it feel almost essential to | me for library developers to consider TS. | | TypeScript has only made debugging easier, much easier since it | catches errors at time of typing unlike the closure compiler. The | sole exception is that debugging is a bit slower since I have to | transpile instead of just refreshing the browser. But I have tsc | set to compile a relatively unminified version of the JS. But if | the slowness gets to me, I can just edit the JS output until I | solve the issue, and then carry those edits over to the TS. This | has never felt like a problem, though maybe his library is | significantly more complicated. | | It is very possible that my opinions are colored by using the | Google Closure Compiler for so many years for type enforcement, | long before TS, which has forced a level of discipline that may | be unusual to JS programmers. But it seems like the problems are | not with TS, but with the labors of architecting coherent and | consistent APIs. But I am not familiar with the author's work to | really judge. | | Feel free to ask me anything if you have questions about library | design + TS. | Bellend wrote: | "I don't know how to make my library typesafe so everyone else is | the problem". | | It's a fucking boring topic that has been done to death since | 2012. Get this deleted guys. | bigbillheck wrote: | I'm not a ts/js person but > we are shifting complexity from end- | developers to library developers | | this is exactly the point of a library in any language. The | library authors do the heavy lifting precisely so that the end- | users don't have to! | | And again, this isn't my domain, but when I read stuff like > How | do library developers debug their highly dynamic and heavy use of | conditional types, overloads? or > Because types can be generated | from other types and the highly dynamic nature of those types, | there's a new class of tests that are required for any serious | typescript project: testing your types. or > I spend more time | tweaking types than I do writing library code. what I'm thinking | is "Doctor, it hurts when I do this". | henning wrote: | > In effect, we are shifting complexity from end-developers to | library developers | | That is the whole reason to use libraries. | | This blog post shows that types make development more enjoyable | by shifting the complexity burden to library authors, who are | vastly fewer in number than library users. It is a clear win. | [deleted] | mpolichette wrote: | I can empathize with the author here that types can be very | challenging to get right, especially with high amounts of | dynamism. | | However, I think that saying it is "terrible for library | developers" is a bit far. I think its terrible for developers who | want to make use of advance types... which ultimately doesn't | depend if you're a library dev at all. | | It boils down to: "Typescript learning curve gets really steep | after the basics" | | The author mentions they help maintain redux-saga. I don't want | to dig on them, but from my personal experience, that library | takes you down quite an opinionated application path with | complexity inherit to it. Heck, just making types for redux was a | pain, let alone adding async and additional composability. | | My take is that the author has chosen complex tech to work with, | and that influences the complexity in their types. | jasonhansel wrote: | Even better: because of TypeScript's many known (and unfixable) | soundness bugs, adding types doesn't even guarantee that there | will be no type errors at runtime. | shadowofneptune wrote: | I would have liked to see examples. My own instinct here would be | to use simpler types, but examples could show why that isn't | desirable. | ryanmcbride wrote: | They probably just keep running into issues that seem really | trivial but are unable to find info on fixing. It's the reason | I put off writing things in typescript until relatively | recently. | | In that vein, if anyone here can tell me how the hell you | functionally map over a typed object by key in typescript I'll | be eternally grateful. | | I always wanna do something like | Object.keys(typedObjName).map(...) but that doesn't work. | | It's so stupid and small and minor and it's never really | prevented me from getting something done but it drives me out | of my mind that I still can't find any clear documentation on | how to do it. | nkingsy wrote: | Object.entries(typedObjName).map(([key, value]) => | `${key}${value}`) | cuddlecake wrote: | Do you have an example for what you want to do, with an | object before and after mapping? I don't understand what you | mean by "functionally map over a typed object by key" | shadowofneptune wrote: | I think I have run into a similar issue. I wrote a lexer in | Typescript. It is table-based, as is the parser that runs | after it. The type for the table looks something like this: | type TokenTable<T> = { plus: T, | minus: T, bang: T, parenOpen: T, | //etc. }; | | I also have a type defined as 'type TokenID = keyof | TokenTable<unknown>;' this makes it possible to check if a | string is a valid key at compile-time. The innermost loop | of the lexer is a for..in loop. This gives you the keys of | the object. One problem: if you try to apply the TokenID | type to the loop variable, you get this message: "The left- | hand side of a 'for...in' statement cannot use a type | annotation." Because of the design of JavaScript, TS cannot | give object keys any other type but 'string', even though | this type seems like a clear match. | | To get the typechecking back on the keys, you either need | to declare the loop variable outside of the loop itself, or | use type casting like this: let token = | ""; let id: TokenID | undefined; for (let | key in patterns) { const match = patterns[key | as TokenID].exec(substring); if (match && | (match[0].length > token.length || key == "EOF")) { | token = match[0]; id = key as TokenID; | } } | | Neither is particularly clean. | lozenge wrote: | Can you define a const array with type Array<TokenId> and | use it every time you want to loop through these keys? | shadowofneptune wrote: | That's a possibility, yes. This is the only time in the | program that a token table is iterated through, however. | Most of the time a table is consulted to pursue an action | in the parser. For example, there's a function table for | when a statement is encountered, another for when an | expression operand is encountered, etc. Each entry in the | table is either an error message or code which completes | the parsing of that statement. The awkwardness above is | excusable when it is encountered so little. When writing | expression-heavy stuff like | 'Object.keys(typedObjName).map(...)' it's more of a | problem. | gnud wrote: | You can approximate something by defining the valid | TokenTable keys as an enum, and using a mapped type for | the actual TokenTable type. | | There's some boilerplate in the definition, but it's | fairly clean and non-repetitive. And easy to use in the | "client code". | | https://www.typescriptlang.org/play?#code/KYOwrgtgBAKg9ga | 1AS... | shadowofneptune wrote: | You put some real effort into the example. It's the | opposite approach of how I did it, yet works just as | well. Thanks! | badlucklottery wrote: | > I always wanna do something like | Object.keys(typedObjName).map(...) but that doesn't work. | | Is this a type space/value space thing? Like Object.keys(...) | is always a string[] instead of Array<keyof TypedObj> like | you might expect? | qudat wrote: | Agreed. I updated the blog to add an example. Copy/pasting | here: | | I spend a decent amount of time in the redux world so `redux- | toolkit` is a great library to see how types are done | *correctly* in a real codebase. To be clear, they do a | fantastic job with types, but the level of complexity is pretty | startling. | | https://github.com/reduxjs/redux-toolkit/blob/master/package... | | That is just one example but the codebase is riddled with | complex types. Also, when you look around, note the amount of | types vs actual code. | | It's pretty common in style guides to never nest ternaries. In | typescript, that's the only way to narrow types based on other | types. It's a mess! | shadowofneptune wrote: | Thank you. | nkingsy wrote: | Along the lines of "just use a dynamic language", we use | unknown and any type for a lot of our internal stuff like | this. | | Strongly typed shell, whatever works core. | meheleventyone wrote: | I suspect it's a case of bolting on types to soupy JS but would | also love to see some examples. I agree with the other | commentators that part of the point of a library is to suck in | complexity but if you're regularly doing high level type kungfu | there are probably bigger structural issues. | brap wrote: | The examples they provide from Redux are insane, and they're | saying it's "types done right"? What is it about TypeScript that | you get these insanely verbose types? I have never seen anything | remotely like this in other typed languages e.g Java. Maybe lack | of overloaded functions? | progx wrote: | "to spend less time making tsc happy" signed! | | I feel i spent more time with that than with the development of | the code ;-) | qbasic_forever wrote: | I wish there were a formal subset of typescript that was go-like | in design. Interfaces, structs, and simple types but not all the | class-based object oriented complexity. A focus on composition | instead of inheritance. | | I like typescript but really worry that it's fueling a boom in | unnecessary complexity and architecture. Are we going to look | back at monster typescript codebases in the same way we look at | monster java codebases riddled with abstract and redundant layers | of complexity because the "design patterns" say to do it? | thdxr wrote: | A great counter point to the thinking in this article is this | snippet by Dan Abramov: https://overreacted.io/what-are-the- | react-team-principles/#a... | | Yes we need better tools as library authors but absorbing this | complexity is our job. The more we take advantage of what | Typescript can do the fewer types the end users deal with and the | better experience they have. | | Making your library's internals simple should be a non-goal. The | number of people dealing with it will be a fraction of the people | dealing with codebases using your library. | latchkey wrote: | This is a matter of opinion. | | I wrote a set of React components that gets 46,000 npm downloads | a month and typescript is a godsend. My library is a bridge | between two heavily popular projects, so my dependency tree is | fairly intertwined. The library solves a real user problem and | does it efficiently. It isn't totally perfect, but it covers 98% | of the use cases. | | I wrote comprehensive tests as I developed everything. I have | good documentation. I have an example app people can play with. I | have codesandboxes. Yes, it was a lot of work, but that's what | building products is all about. | | I've maintained this library for over 3 years now. I've upgraded | it many times as the underlying dependencies have changed. A few | times with backwards incompatible changes. I get outside | contributors doing great work. | | I do releases on a regular basis, without worry, because my test | suite is that good and because I know that if the underlying | types change, tsc will catch that too. These things have saved me | countless hours of work finding bugs and fixing issues. | | In other words, I call bullshit on this entire blog post. Ignore | it. Typescript is the correct solution. What is the alternative? | Just using JS and letting things break without knowing it? Come | on. | | Learn how to use types. Learn how to use a professional IDE, like | IDEA, that shows you what's wrong as you develop. Learn how to | write tests. Learn how to use a debugger. These are all things | any decent developer should know and practice on a regular basis. | gkiely wrote: | > Typescript is the correct solution. What is the alternative? | Just using JS and letting things break without knowing it? Come | on. | | Surely that's not the only alternative. | | Here's a few off the top of my head: | | - Being able to write complex types in a syntax that more | closely resembles JavaScript. Using Array length for counting | or nested ternaries for if logic gets old fast. | | - Being able to debug types, not console output, a real | debugger. See: | https://twitter.com/MarcJSchmidt/status/1539787500788613120 | | - More comprehensive documentation on writing advanced types | | - A typescript specification | dvt wrote: | > Being able to write complex types in a syntax that more | closely resembles JavaScript | | Yikes. This is how you end up in preprocessor hell. Macros | are generally _not_ a good thing. (Fyi: TS types are already | Turing-complete which is arguably a mistake.) | | > Being able to debug types, not console output | | Eh. It's not like Java has a "type debugger." Why is this | needed? Why are your types so complex? Weird ask. | | > More comprehensive documentation on writing advanced types | | Really beating the same drum here. | | > A typescript specification | | What does this mean? We have a pretty clear typescript | spec[1]. | | [1] https://github.com/microsoft/TypeScript/blob/main/doc/spe | c-A... | gkiely wrote: | > Yikes. This is how you end up in preprocessor hell. | Macros are generally not a good thing. | | Not suggesting macros, rather an alternative way to define | types. A function that accepts and returns types. Seeing as | typescript has a JavaScript interpreter I thought it might | be feasible but I'm just spitballing it. | | > Eh. It's not like Java has a "type debugger." Why is this | needed? Why are your types so complex? Weird ask. | | I don't buy this argument. Writing any type of complex | types with recursion, arrays or ternaries sucks and it will | take more than "this is how Java does it" to convince me | otherwise. I like to debug with a debugger, not my head. | | Take a look at the examples in the article to see some | complex types and read some tweets from library authors | complaining on twitter. I can find some examples if you're | interested. | | > We have a pretty clear typescript spec | | I hadn't actually seen this and it looks interesting but | seems pretty out of date, Typescript 1.8? | latchkey wrote: | > - Being able to debug types, not console output, a real | debugger. See: | https://twitter.com/MarcJSchmidt/status/1539787500788613120 | | That is really nice. I'd love to see that live. | david422 wrote: | > Just using JS and letting things break without knowing it? | | And if types change, and it still works, it's basically working | by accident. | oivey wrote: | That's a pretty underspecified comment for a static typing | enthusiast. You're referring to polymorphism. | | And to be completely clear: you can have safe polymorphism | without type hierarchies via things like type inference. | latchkey wrote: | If types change, it doesn't compile and I can't do a release | in CI. | | I don't consider that working. | david422 wrote: | I'm agreeing with you here. If you use plain JS and change | the underlying types that are being passed around, but | things are still working, you're basically getting lucky. | latchkey wrote: | Ok, got it. Apologies. | | If you write comprehensive unit tests for your | javascript, that should effectively also work too... this | is what ruby developers pretty much had to do. | | We can see that over time based on the decline of ruby | popularity in general, that fell out of style though. It | is too easy to make a mistake and everyone was just | duplicating what a compiler does for us. | | I can't tell you how many hours I would sit there pair | programming with other ruby developers just trying to | figure out what 'type' an object is. Mind boggling, but | it was good to get those high paid consulting hours. | arinlen wrote: | > _And if types change, and it still works, it 's basically | working by accident._ | | So the Liskov substitution principle now passes off as | "working by accident"? | 3qz wrote: | Do you get paid for any of this? | latchkey wrote: | Not directly. I've been developing open source for almost 30 | years now (I co-founded Apache Java/Jakarta). I consider that | the experience gained from developing extra curricular | projects pays for itself in other ways. For example, I've | never had to interview or even look for a job and I've never | been laid off. | mmmpop wrote: | Invest in yourself and you'll never work a day in your | life. | somenewaccount1 wrote: | Tell me that you didn't read the article without telling me you | didn't read the article. | | tl;dr: the author loves typescript and thinks it brings a ton | of value to the end user. He also agrees that he wishes he knew | more about complex types which are needed in libraries - and | that's his primary gripe specifically as a library developer - | is that the knowledge base for library developers is very | sparse. | remram wrote: | Tell me you didn't read the HN guidelines without telling me | you didn't read the guidelines. | | > Please don't comment on whether someone read an article. | nightski wrote: | There should be another guideline about commenting on | whether someone read the guidelines. | somenewaccount1 wrote: | to be fair to me, i did read them....i just don't care that | much. | latchkey wrote: | > TypeScript is terrible for library developers | | That's the title. That's the whole premise of the post. | | > his primary gripe specifically as a library developer - is | that the knowledge base for library developers is very | sparse. | | No, it isn't. | mattmanser wrote: | It's the first of his 5 gripes. There's only 5 of them and | they're titled. A few of the other gripes also mention the | lack of documentation, so it definitely appears to be his | main gripe. | | I agree with GP, you don't seem to have read his article | and knee jerked a reply. | | And all you have to do to prove your point is to link some | great documentation explaining how to make complex types | for libraries and how to test them. | | Which you haven't. | latchkey wrote: | What I said is that I call bullshit on his 5 points. None | of them are valid and they are just opinions of a | developer who hasn't put the effort in and isn't | providing concrete examples of what is actually wrong. I | actually find it difficult to understand why this is even | on the front page of HN, maybe it is a slow news day? | | I feel that my point is proven with a successful multiple | year open source Typescript library. | | Great documentation? That's subjective. I'd start with a | deep reading of the official docs as they are quite good: | https://www.typescriptlang.org/docs/ | westoncb wrote: | > I feel that my point is proven with a successful | multiple year open source Typescript library. | | That only proves that it's _possible_ to do, not that the | language facilitates it or that certain modifications to | the language /ecosystem wouldn't greatly improve the | process. The point of the article is the latter, not the | former. | andrewmcwatters wrote: | What package do you publish? https://github.com/lookfirst/mui- | rff? | Shorel wrote: | I agree so much with you. | | I would even say: most libraries used for just about anything, | are written in C/C++/Java, all of which are typed languages. | | The post's author simply lacks the education and experience to | actually have a valid opinion on this topic. | auggierose wrote: | Good for you. I was somewhat disappointed with TypeScript in | the beginning, because I tried to use the type system like I | would use it in Swift or Scala. That's not really a good idea. | If instead, you view TypeScript as a smoother implementation of | JavaScript + JSDoc, then it becomes a really powerful tool, | which I like more and more! | hmsimha wrote: | > In other words, I call bullshit on this entire blog post. | Ignore it. Typescript is the correct solution. What is the | alternative? Just using JS and letting things break without | knowing it? | | It seems like you and I read a different article. The post I | read did not advocate for going back to JS; rather it advocated | for better tooling and documentation (with examples, ideally) | for library developers Conclusion I | love typescript and think the team working on it are | incredible. Typescript has completely changed the FE landscape | and wouldn't want to dismiss its contributions. But as | a library developer, we need: - better documentation, | - better tooling, and - to spend less time making tsc | happy. I shouldn't have to read the typescript compiler | source code in order to figure out why it's resolving a piece | of my code to a specific type. | | I developed a library used by the React app I work on to cache | network results in localstorage (to reduce the number of | expensive requests made on page refreshes) and generate React- | Query query-functions to read the result from localStorage | first before fetching from the network as a fallback (if the | data is stale or uncached). | | It's not an especially large library, but I felt all of the | pain points the author describes. It's great that you didn't | with your library (and I'm sure there are large classes of | libraries whose authors wouldn't feel these pain points), but | it's a very real issue if your dealing heavily with generics, | serialization/deserialization, and/or complex type interactions | fullstackchris wrote: | Do you have an example? I've seen TypeScript handle crazy | nested types (and generics) with ease. | hmsimha wrote: | Perhaps the React-Query source itself is a good example of | when "simple" is still not "easy to read/write": | | I picked out this file pretty arbitrarily: https://github.c | om/TanStack/query/blob/main/packages/react-q... | | The author of react-query seems to be very clear at | communicating how the library works and the library itself | provides a great developer experience, but it's also an | example of how much work can go into correct typing in | library code. | drewpayment wrote: | You answered your own question, learn how typing and generics | work and you wouldn't have had those issues. | | The entire article persecuted TS because the author wanted to | learn quicker with little effort. Writing TypeScript in a | node, app or library makes no difference. It is a language, | NOT a framework. | hmsimha wrote: | Yes, and a great way to do that is to read documentation, | of which Typescript is frequently lacking. For example, I | got a suggestion in the discord recently to use "generic | parameter defaults" for a problem I was having, which were | documented... in the release notes for Typescript 2.3... | and no where else: | https://www.typescriptlang.org/docs/handbook/release- | notes/t... | arinlen wrote: | > _Yes, and a great way to do that is to read | documentation, of which Typescript is frequently | lacking._ | | I completely disagree, and I was surprised by this sort | of comment. | | Find me a single programming language whose docs are as | good as TypeScript's docs and reference. I'd be surprised | if you could come up with a single example. | stevage wrote: | JavaScript | olliej wrote: | > There are a lot of reasons why typescript sucks for library | developers, but at the end of the day it reduces developer | productivity. In effect, we are shifting complexity from end- | developers to library developers. | | Yes. The point of making a library is almost entirely to take the | complexity load from developers. | simlevesque wrote: | If your library was built without thinking about types, yeah | it'll be ugly when you add them after. | z3t4 wrote: | Libraries need good documentation with reference and code | examples. What libraries don't need is complex type annotations | that wont make it into the compiled code anyway. The worst | documentation I've ever seen is the one that use the types as | documentation, looking at you the Language server interface. | Loeffelmann wrote: | > It's pretty common in style guides to never nest ternaries. In | typescript, that's the only way to narrow types based on other | types. It's a mess! | | I really wish there was a kind of type builder where I can use | ifs and switches to build complex types. For me that would | already take away a lot of the pain of maintaining and writing | complex types. ___________________________________________________________________ (page generated 2022-08-23 23:00 UTC)