When to Use TypeScript – A Detailed Guide Through Common Scenarios
khalilstemmler.com
khalilstemmler.com
> if you care about the code, you need to have unit tests for it.
That's not really true. You can test at a differently granularity and still have the same confidence in your code. Scores of brittle unit tests crowding your productivity is not what you do to code you care about. I wish people would just stop propagating this detrimental rumor that is backed by zero evidence. I prefer to keep my unit test suite lite and sweet (try to test what is very sensitive at the unit level - easy to miss or algorithmic things) and then try to get the most out of my acceptance test suite. Sorry, but I just worked on too many systems where the unit tests suite breaks no matter what you touch and you spend more time updating the tests than actually getting shit done.
IMO appreciation for automated test ROI is a great litmus test for a "good programmer".
But unit / unit++ tests are not just for you, they are for me, they give me confidence I can touch your code without inadvertently breaking something critical that you may have missed in your acceptance tests. It gives you peace of mind to be able to refactor. An acceptance test may tell you they something broke, but a good unit test will show you exactly where.
So you can miss things in your acceptance tests but can't miss them in your unit tests? Why do you feel more confident with unit tests than with acceptance tests? Maybe because unit tests break more often so they are providing you with a false sense of security? Do you know that when a test fails for a reason other than the specified assertion its a called a false negative and the failure is meaningless? Do you know if your tests fail and no functional or non-functional requirements of the program has changed the failure is also meaningless (you are testing implementation)? Ask yourself how often does your group give you time to do these hypothetical large refactoring that give merit to large unit test suites? How many times, even, do you do small refactorings and find all the test have to go away or significantly change anyway because they made some implementation assumption that is no longer true? How many times have you gone into a test suite and have seen that its been patched up so many times that you can swear its testing something but you just don't know what? I know that Uncle Bob tells us about test for refactoring and this and that, but I just don't buy it any more. The emperor is naked god dammit. Uncle Bob and the like are very famous because people swallow their anecdote without due scrutiny.
That said, you should proliferate unit tests for algorithmic logic. That can be a small subset of your code, but a large part of your domain (depending on richness). Integration testing can be written manually, or you can pick a compiled language that does a lot of the lifting by type checking and producing the binary. Acceptance tests guide the overall happy path and can protect against weird regressions.
I feel like I ought to write more on this one of these days, but I have a few testing pet peeves, as far as tests you shouldn't write. Don't write tests that are a copy-paste of the code you're testing. Don't write tests to validate things that should be proven by your type system and lack of compile/parse errors. Beware of tests so tied to implementation details that they make your code harder to refactor. Don't write tests for things talking to external services - the only real test is that it correctly handles the actual service responses.
Here's where I agree with his statement:
On the backend, I agree that all of the domain-layer code should be tested. This is a hard requirement for me. It's also code that has 0 dependencies so it should be easy to test. TDD pairs really nicely with DDD.
Unit tests give you the needed confidence in order to do refactoring. You can't refactor code without tests. If you do, there's a risk involved. Therefore, in order to safely improve the design of existing code, it needs to have tests. This "detrimental rumor backed by zero evidence" is one of the fundamental takeaways from Martin Fowler's book on "Refactoring" in addition to Uncle Bob's chapter in The Clean Coder on Unit Testing.
Here's where I disagree with his statement on 100% test coverage.
I used to spend a lot of time writing brittle tests by testing front-end UI code. I used tools like Selenium and Cypress. Because the front-end is the most susceptible part of a system to change, I found myself spending an equivalent amount of time maintaining these tests in addition to adding new code. This is a hard place to find a balance. In Angular, I merely ensure that I write tests for services. In React, I spend a lot less time writing Enzyme tests on rendering and a lot more time testing the redux operators.
Uncle Bob clears this up in his book by saying USUALLY, it's not necessary to write UI tests. I'd say by UI tests, he means "rendering tests". His solution is to ensure that you have a way to run acceptance tests that work through the API, as it should be a lot less susceptible to constant regressions. This way, you're essentially testing the features that the API is executing. If you're using DTOs, the inputs and outputs of your system should remain relatively the same anyways, and you should spend less time changing old tests, and more time adding new ones.
See this is where the conversation goes south. You will use the phrase "unit tests" when it suits you, and then, like a magician using indirection, generalize to the word "tests" when it suits you.
Why are you listening to Uncle Bob so much?
I mean you can do as you like. However, the strong "Uncle Bob" style assertions should probably be left out of most articles in favor of a more humble "this is what worked for me/us" approach. Universal rules for any discipline are very hard to come by. Sharing what worked for you on your projects is great but it becomes somewhat obnoxious when you try to generalize it to universal rules that works in all environments for everyone.
Also, I think that large unit test suites, where most of the tests are redundant, come about in competitive environments where if you try to make a commit without a test some other competitive coder will try to use it as a "I know better" stepping stone and call you out on the change - "Where is the test". After 3 years of this behavior what do you think your going to have. A nice clean suite of tests or a monstrous big ball of stubs, mocks, faked, copy pasta that resulted from defensive social coding.
Honestly, write a light suite of tests that get to the point, and keep your ability to code swiftly and make changes quickly. As the code matures you'll find the trouble spots and focus testing on those areas. Don't blindly follow methodologies that are going to have you writing large test suite for version pre-alpha 0.0, and now your updating a large test suite for every micro-change just to make it to beta 1.
Here is an old article by DHH where he rails against unit testing dogma. Ironically it was ideas from the Ruby community that formed a lot of the basis of the mad dog McCarthyan style unit test dogma. https://dhh.dk/2014/tdd-is-dead-long-live-testing.html
This one never ceases to amaze me. Any codebase is small, until it gets big. And once it's big, the effort to rewrite is hard to justify.
You don't build a house and add the foundation later.
I always focus more on the "foundation" part, in this case, it's common React Hooks and its API for the components to use.
The good part is, if some hooks are wrong, we can just write new one and replace old ones within old components without the need of taking care of the old wrong Hooks.
This is why I always over engineer a bit, especially on new projects. I'll happily, for instance, add a framework before it's strictly necessary. I've never regretted that decision in the end.
If people here want to use a 5 gallon bucket on an initial 5 gallon job, go for it. Best of luck to you. I'll be over here starting with a 10 gallon bucket and not sweating when the customer needs to add another gallon...
The problem with overengineering is that you don’t have a good grasp on a generalized architecture until you have two or three use cases. Of course anything dealing with cross cutting concerns like logging and authentication it’s easier to add up front.
I don’t know any modern front end frameworks, but when working within a team, I’m all for opinionated popular frameworks. It’s s lot easier to onboard people and from a completely selfish standpoint, developers who are concerned with their careers want to be able to put a transferable toolset on their resume.
There's a world of difference between adding a framework and over-engineering something that could have used a simpler solution.
My experience has been the exact opposite of yours, whenever I added something complicated, it either never got used and unnecessarily complicated the code, or when the time came to use the fandangled cleverness I lovingly wrought, it never quite met the need I actually ended up having.
So I stopped doing that years ago and now always write the simplest code. Never regretted it. I will add frameworks early though.
Designing a spring quartz scheduler backed by a DB when a simple cron job would do? That's overkill.
It's often a matter of how much you think the job is likely to expand, how much you should frontload work to make that simple.
I often see good, actually simple solutions get turned down due to people saying "that's too complicated for what we need", only to later hear, after a couple of months in production, that nobody wants to touch the codebase anymore since it's unapproachable and the rewrite will not happen due to high risk since it's critical production code now.
What I'm referring to in my previous comment is when people underestimate the complexity added by the easy and quick solution today vs the high cost over time. Specially in critical projects.
I also have noticed that if you write daily using some code style (for example using types) it takes time to switch to another style, for example without types. So you are better of using types, because you are used to it and you will code faster that way anyway.
I also use HOCs a lot and it's painful with Typescript.
In my experience, if you spend a few weeks in the "exploratory" phase writing ES6, rewriting to TS won't take more than one or two days.
Nowadays I'm a lot better at Typescript and will use it from the get go, but for someone who is less skilled in it, it might much faster to produce working code first and add types later.
do y'all work on the linux kernel or what ? most of the projects I've seen across multiple jobs are less than 5000 loc. And they never will "get bigger" because they are not "products" that get evolved over time.
But many tools, flow, Python, TS allow you to incrementally add types to an existing codebase. That way refactoring isn't some herculean effort, but something you can do for new features, or gradually over time.
Here's some thoughts on what has contributed to that so far:
1. TypeScript pushes you towards its own build pipeline (based on tsc) that doesn't play nicely all the time with mainstream JS build pipelines (usually based on babel).
With babel 7 came @babel/preset-typescript, which I hoped would narrow the gap, but so far it's very clearly a second class citizen in the TypeScript ecosystem, with new features built only with tsc-based pipelines in mind (Project References is the one that stands out because we sorely need it for our monorepo codebase, but can't use because we chose to adopt @babel/preset-typescript), and having personally ran into several issues stemming from what seems to be fundamental incompatibilities between tsc and babel that have no real workarounds (here's one that I can remember off the top of my head: https://github.com/babel/babel/issues/8361).
The reason this is so frustrating is TypeScript could have been just a type checker, like Flow. But instead, it had to introduce it's own compilation tooling which is still vastly inferior to babel in terms of overall flexibility and extensibility. All of this seems to be due to what I believe are a few fundamentally poorly thought-out decisions at the beginning of the project to allow TypeScript to specify its own language features that have runtime semantics (things like Enums and class visibility modifiers, neither of which are anywhere close to becoming standardized in JS-proper, by the way, and both of which are completely orthogonal to TypeScript's main responsibility of type checking), and implementing them using a separate build pipeline instead of as extensions to babel & its own type checker.
If I were to use a JS type system for a new project today, I'd personally choose Flow over TypeScript in a heartbeat because of this.
(this is getting a bit long so will continue in a reply)
I do agree on the extra stuff beyond types (enums, etc) being a mistake that Typescript would be better off deprecating at some point.
Babel has always caused me headaches. Setting up Babel-based build systems is troubling—the same setup will work sometimes and not others even on the same machine. TypeScript vastly simplified working with modern JS for me. It’s consistent, requires far fewer dependencies, and is much more succinct.
YMMV, of course...
If you’re talking about front end libraries like React you’re better off ditching Babel altogether and going with something like Parcel. It just works.
The TS developer experience ya sim proved working with JS enormously for me when I have to do it, and nudged me toward simpler, smarter development environment setups.
I can’t speak for you or anyone else. Just adding my two cents.
That doesn't mean one's necessarily fundamentally better or worse than the other, but the great thing about extensible tools is that they can often be used as building blocks for other more opinionated tools that abstracts away configuration details from the user by providing an opinionated set of defaults, and still offer the ability to customize when users' needs are not adequately met by the defaults.
Case in point - Parcel actually uses babel under the hood:
https://parceljs.org/javascript.html#default-babel-transform...
All it does is configures a default set of plugins & presets for you. You can override any and all compilation behavior Parcel provides by default simply by adding the appropriate babel config files.
TypeScript could have done the same with their build tooling, by extending babel with plugins/presets and providing the exact same set of functionality to users as what tsc's is capable of currently in terms of compilation features (which is currently afaik a strict subset of what babel + its plugin ecosystem is able to provide).
That would give users the exact same out-of-the-box experience as tsc does currently, and offer them the opportunity to extend compilation behavior with additional plugins and presets when the need arises.
That extending the compilation behavior of tsc, after so many years of development and wide adoption, is still not even on the roadmap right now is the core of my disappointment with them choosing to develop their own _non-extensible_ compilation tooling vs just extending babel, where they would have been able to offer extensibility for free from day 1.
A prime example for this is ramda's pipe function: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
A simple function that composes functions together in reverse order requires:
1) The definition to be an exhaustive list of all the variations of input functions that pipe can accept (which of course would technically require an infinite number of declarations to fully specify, so ramda had to call it quits at 10).
2) The user of pipe to specify output types for every single function being composed at every step, even though that should be possible to infer using the return type of each function in the pipeline.
These kinds of limitations plague our codebase everywhere we try to define higher order functions in our own code as well, because it means we have to ask users of the function to specify the result of the function argument as a generic parameter even though it should be perfectly inferrable from the function argument itself, and the noise buildup becomes exponential as you start composing higher order functions together due to pipe suffering the same limitation. The issue also rears its head when using higher order components with recompose, which we make heavy use of as well.
I'd love to hear how other teams working with functional programming in TypeScript work with these limitations. Perhaps we're missing something fundamental that could vastly improve our experience?
It's well known that positional semantics don't scale when it comes to argument lists, because when changing the API of something with a positional argument list, adding an argument in an arbitrary position becomes a breaking change that requires all usages to be updated.
Unfortunately, part of the fallout from point 2 means we often need to define higher order functions that require a decent number of generic parameters, all of which have to be positional due to this limitation.
There's a hack that we discovered that allows you to have poor-man's named generics by using a single generic argument with named properties, inspired by this comment: https://github.com/Microsoft/TypeScript/pull/23696#issuecomm...
But it's incredibly verbose, and doesn't support optional generic arguments or defaults as far as I can tell. We still use it despite this, because we believe the verbosity and having to specify every generic argument is the lesser of two evils compared to offering APIs that requires breaking changes for every addition. But that we have to make this choice points to a glaring omission in the design of the type system.
I’d also like to point out that functional programming is tremendously helpful for solving these types of problems, especially when combined with use of modules. TypeScript is absolutely capable of modeling, checking, and otherwise handling types in a functional programming context. — this is one of its best strengths in my opinion, and one reason I now prefer TypeScript over C#.
TypeScript is a transpiler for JS. C# is a server side language. Can you please clarify how former is the replacement for latter?
Typescript is not even necessarily the transpiler nowadays. I use Babel for that and use Typescript exclusively for static type analysis in CI and during development.
Node.js is very popular, making TS/JS available 'on the server side'
I posted about the specific issues I ran into here, and they seem to be pretty fundamental w.r.t the ability of TypeScript's type inference to work with function composition in general: https://news.ycombinator.com/item?id=19600475
But the fact that you (and presumably plenty of others) seem to be having a great time with functional programming in TypeScript leads me to believe that there might be something I'm missing that could vastly improve my experience with functional programming in TypeScript as well. I'd love to hear any further thoughts anyone might have on this topic.
I've noticed "functional programming" turned into an umbrella term.
For some people it means Haskell: strongly typed lazy evaluation with a focus on ADTs and functions that operate on them (i.e. methods) to achieve composition. Heavy use of pattern matching.
For others it means Clojure: dynamic language that favors raw duck-typed data manipulation. Heavy use of macros.
Then there's the pointfree crowd: very little use of literal functions, most of them are just composition of curried/partially-applied primitives.
For many it just means "I use map/filter/reduce".
And for most, it's just a mix-and-match of subsets of all those.
My experience matches yours: Typescript is mostly fine with map-filter-reduce and that's it. I'd love to be proved wrong though.
Could you provide some examples?
I imagine adding types gradually as a design solidifies, rather than slow velocity early on. Best not to be dogmatic on these things.
Types don't slow me down, and I can't imagine working without them.
A wild Idris programmer appears
I'm not sure TypeScript is good enough for doing that (I don't know TypeScript that well), but it's simply much better than starting with code.
Also coming from JavaScript development first, TypeScript made learning C# and templates a breeze.
Fine.
> look how it works,
Beutiful except it doesn't work yet because it's just types that make some shape. You could literally draw the thing with pen and paper.
> go bother with implementation.
And only then discover that something you have thought of as implementation detail is one of the main problems with your solution.
No problem. I'll just refactor some type. It's easy. Less beautiful but at least it'll work. Except it doesn't. Butchering your elegant design for hours you come to hard earned realisation that the shape you imagined that you wrote and carefully named all your types to represent actually has nothing to do with the one you need.
If you are a bad person you ship your mangled typed monstrosity that you just barely made to work with traces of your struggle fossilised for any future maintainers.
If you are a good person you toss everything to into trash (save for few hard earned implementations) and start fresh with knowledge and humility wishing you worked with TypeScript where you can start with implementation and only after you figured out the hard parts, add types that reflect your solution (finding few additional bugs in the process and getting more confident about the thing you wrote).
Paper does not tell you when something you draw is wrong.
> And only then discover that something you have thought of as implementation detail is one of the main problems with your solution.
What is no different from learning that something you implemented is wrong, except in that you spent less time to get the problem. Or do you expect some magical designing tool that will lead you to the right solution before you understand your problem?
With some things it does. At least better than your brain does. Typesystem does it even a bit better. But you end up with a picture not a program nonetheless, before you start writing implementation.
> What is no different from learning that something you implemented is wrong [...]
What is different is that with all the types you wrote, the process of problem discovery that happens during implementantion is stifled by what you wrote so far. Also you have additional temptation to contort your types and to leave it in a bad state because you wrote so much already and you'd very much want to avoid tossing that out.
My point is that you discover problem through experimentation and types make experimentation harder because they make the code in some ways harder to change. TypeScript avoids that by allowing you to use as much types as you want at any stage of problem/solution discovery but not forcing you to.
The only benefit of a plan is that you may discover it's wrong, and it's easier to throw away than the done thing. If you are not willing to reform or throw away your plans, you are doing it wrong.
And you will almost always know what type a property needs to be. Name? string. IsActive? boolean. Last modified? Date. Etc.
Also typescript just has number, so you're not even faffing around thinking "int, float, long, double, decimal, unsigned, signed" when adding a number. Although this is ultimately a bad thing.
Because you have to hit more keystrokes to write your program?
That doesn't compute. typing is the thing you do the least amount of when programming. In all best practices we are taught to not save keystrokes. Name your variables expressively. write small functions. document code. Write tests. All ""excess"" keystrokes nobody questions. But when you have to type " ... : number" it's slow velocity?
In my opinion people vastly over-estimate productivity boosts of saving keystrokes. It's the tedious, boring part of the work that you would like to skip entirely, hence excess keystrokes feel way more "slowing you down" than they acutally are.
Although it doesn't matter, you actualy save keystrokes: autocomplete, auto-refactors and less low level unit-tests compensate the couple of type annotations easily.
That usefulness is diminished when you are fighting the type system instead of getting things done.
Like when you have to make exceptions in typescript by using Any. Like when an interface for a js library is missing and you don't want to do all that work.
Or when an API returns a field with a different type in between two calls and you have to make exceptions in the code when parsing with jackson in java.
Code is read much more often of course. You may argue you can read a fifteen-page book as fast as a ten-pager, but it's pretty clear that's not the case.
Of course tools and inference change the balance, but even so I avoid putting code into the the kiln until the design is fully understood and at least satisfactory. And that can take a few revisions.
And figuring that out becomes a massive time suck when that's a constant across an entire code base. Statically typed stuff means I can very quickly find that out even if it's the first time i'm exploring that particular area of the code.
Some dynamically typed languages support type annotations that are only enforced dynamically; Julia is the primary example of such a language ATM.
I also feel unit-level tests are less necessary with TypeScript. I used to write a lot of JavaScript tests that essentially just confirmed that "this sh*t is all tied together correctly". With TypeScript, if a code change or refactor broke something structural, you know immediately!
Especially with strict compiler settings, including strict null checks, and the good type conventions they encourage. To give a small example, if you have a value that can either contain an error or a successful result, you can model it as the union of a success and error state. This prevents you from accessing the value until you check the result is successful (and the compiler will nicely infer the type in subsequent usages once you do.)
For me, with rigorous compiler settings and a good proficiency in type modeling, I've reached the point where I rarely have any runtime errors at all once the code compiles. Sure it might take 2% more time to write code and think through my data model, but I spend 95% less time debugging it and the end result is much more maintainable. Even using typed languages like C# and Go now leaves me slightly dissatisfied!
Relying strictly on typing is dangerous as you might've given wrong type from data coming back from database queries and you can't tell until it has run.
I don’t know about the rest of the world, but I’ve never once been slowed down by a compiler saying “It won’t work like that. Don’t waste your time”.
> I don’t know about the rest of the world, but I’ve never once been slowed down by a compiler saying “It won’t work like that. Don’t waste your time”.
I'm right there with you, I really don't understand this train of thought that catching errors faster somehow makes you slower. I like having the compiler there to catch my errors when I make them rather than some number of minutes/hours/days/months later when everything is on fire and I've forgotten the context of what I was doing.
Maybe learning a statically typed language (nearly) first damages your brain irreparably, and there's no longer any hope for me.
Under dynamic typing, you just do a small part of the job, yet the program builds; you can get in there and try things. Decisions about how to proceed can be guided by errors you run into when you step on the things broken by the partial refactoring. Seeing things working right away can be a motivator to get the whole thing done. Or it can help you see that, oops, the change is not worth doing or bad for whatever reason: you dodged a bullet with a small amount of sunk cost (the few changes you made that you can throw away without a whole lot of regret).
Static typing helps with small refactoring where the ripple effect is small. The compiler pinpoins things that have to change; they happen to be few in number, and don't have too many ripple effects of their own. If your change breaks everything, then it's moot; the compiler tells you there are breaking changes everywhere.
There is another angle: under dynamic typing, changes can be done in ways that things continue to work. You can write modules that have "absolute genericity": they work with objects of any type without being recompiled. Such things are bullet-proof against all conceivable refactoring.
I’m reading this and I have just no idea what to say.
With static typing you don’t have to be very considerate in this work, because you will have a compiler telling you if anything is broken, not to mention refactoring-tools which can help you reliably and automatically make those changes across your entire code base in a verified 100% correct way.
With dynamic typing however, you just have to guess and pray.
Clearly static typing is the superior option in this specific scenario where you are calling out static typing to be a disadvantage.
So what am I missing?
[0]: https://en.wikipedia.org/wiki/False_memory#Commonly_held_fal...
A good language has a run-time type in every object, regardless of the type checking system, and allows programs to be run safely even if they are incompletely checked or if inconsistencies have been positively identified.
I have exactly the opposite experience here. If you're making a sweeping change to a core data structure, I would feel much more comfortable making a large change in a statically typed system. It would take much longer for me to feel comfortable releasing a change like that in a dynamically typed system.
> Under dynamic typing, you just do a small part of the job, yet the program builds; you can get in there and try things. Decisions about how to proceed can be guided by errors you run into when you step on the things broken by the partial refactoring. Seeing things working right away can be a motivator to get the whole thing done. Or it can help you see that, oops, the change is not worth doing or bad for whatever reason: you dodged a bullet with a small amount of sunk cost (the few changes you made that you can throw away without a whole lot of regret).
Is this how you work? That's terrifying. How much time do you have to spend poking and prodding after you make a change like that in order to be sure you've fixed everything? How much time do you spend tracking down the source of bugs caused by this weeks or months later?
> Static typing helps with small refactoring where the ripple effect is small. The compiler pinpoins things that have to change; they happen to be few in number, and don't have too many ripple effects of their own. If your change breaks everything, then it's moot; the compiler tells you there are breaking changes everywhere.
If your change breaks everything, it's too much to change at one time. Period. Smaller refactors that deliver some amount of value are so much faster than cleaning up for months after throwing caution to the wind.
To use the example of a change to a core data structure: If you want to make a breaking change to a given struct, the first place I'd look is to see if you can convert between the old style and the new style seamlessly.
If that's possible, then you copy the data structure, make your changes, and add a method on each to convert to the other. Then you can slowly roll out that new data structure to the rest of the application until the compiler allows you to delete the original. Speaking from experience this route is so much faster than swapping it out at the source, fixing it up everywhere, and hoping things continue to work as they should.
If it's not possible, not all is lost, but I'd need to see the specific example to give advice on what to do.
> There is another angle: under dynamic typing, changes can be done in ways that things continue to work. You can write modules that have "absolute genericity": they work with objects of any type without being recompiled.
If your code can work on literally any group of assorted types, but still do something of value, then I'm interested to see what it's doing. Sounds like it doesn't do much of anything except pass pointers around. Great for a collection struct, but that's about it. As soon as you need to get things out of it and do something of value, you'll need to have some idea what type you're getting back.
> Such things are bullet-proof against all conceivable refactoring.
I am extremely cynical of any promise regarding code that is "bullet-proof against all conceivable refactoring". OOP famously promised that for many years, but my experience has been that it's the cause of far more refactoring pain than it saves.
Hold on. Are you conflating OO languages with static typing? If your experience with static typing is Java then I actually think I understand where you're coming from now.
I wish I could be convinced by the likes of Rich Hickey that types don't actually help, because he's so smart and charming and eloquent, but my experience screams the opposite.
The usual saying is that faster development is more important because your business probably won't get users anyways. As if Ruby is going to give you a two year head start before any chickens come home to roost.
I don't know about that. Maybe you will get a half-day head start on me if I'm using a statically-typed language I haven't used before and I'm digging through old projects to remember how to write my .gradle hello world.
I don't want to commit to refactoring a large amount of code for the sake of a small change, just so that some code-writing robot gives me permission to run the whole thing.
Suits me.
Welcome to 1985 folks :-)
TypeScript is a great tool.
At the same time, people have written apps with vanilla JavaScript for a very long time now, and it works just fine. If types are really that big a deal that you have a hard time writing an application without a compiler checking your types, you should reevaluate what you're doing. It's not "dangerous" to use plain ol' JavaScript, and implying that others are foolish for not using it reeks of software snobbery.
Whoa, you mean people really still use plain JavaScript, bruh? I mean, don't you need like punchcards for that? That's how grandpas program, bruh. You can't even, like, scale an app without type-checking. An app written without TypeScript is like a house of cards, dude.
Implying that everyone has exceptional short term memory, and reading old code has virtually no cost.
Also keep in mind that this both enables, and follows a trajectory of increasingly complex frontend applications, previously a lot of interactive stuff was done server side.
Patently absurd, if you take it as a strawman, sure. You ended up using that to paint the complete opposite picture, which was essentially that Typescript doesn't actually add any real value, only "perceived" value. Which is true, if all you value is the execution environment. Keep in mind that Typescript was not the first attempt at trying to "tame" Javascript. One example that comes to mind is Coffeescript.
The natural reducto ad absurdum is thus, well why aren't we just writing in ASM, it all boils down to that anyway, right? People were doing fine then too... Could it also be said that accusing people of needing "crutches" is also snobbery?
This is a great analogy for my experience with JS projects. Most bugs are found at runtime, which is incredibly frustrating and time-consuming.
As you say, there are still niches where C is good, and C is going to be around for a very long time because it's used for so many things. To claim that someone can't write a well-built application that is maintainable and scalable is asinine. I myself probably wouldn't choose to write an application in C, but that doesn't mean I'm going to thumb my nose at anyone who decides to write applications with C. C is perfectly fine, and if someone is frustrated with it, then maybe it's just not the right programming language for them.
" people have written apps with vanilla JavaScript for a very long time now, and it works just fine. "
No, it doesn't, or else TS would not exist.
TS just outclasses JS on almost every front, it's not a religious statement, it's generally true: TS is the standard, it just has too many advantages over JS to allow JS inside the roost.
Though article is only misleading to the point wherein they're talking about TS vs other languages, for solving other problems.
> No, it doesn't, or else TS would not exist.
Think about what you just wrote there.
It's also entirely possible that a given tool can work fine, but other people can build on top of it without irrationally claiming that said tool doesn't work. Elixir and Kotlin are examples of tools that improve upon other runtimes(Erlang and Java), but I've yet to hear anyone from either language community act so dismissive to anyone who chooses to write applications in Erlang or Java as some TypeScript users are towards vanilla JavaScript users.
Speaking of software snobbery, what does this statement reek of? This attitude is just as condescending and utterly non-constructive as the fanboyism you're describing.
I'm currently building a fairly large application using TypeScript both on the front end and backend specifically because I "have a hard time writing an application without a compiler checking your types". It's working great but according to you this is a valid reason to reevaluate what I'm doing?
It also has amazing tooling in intelli-j. Code completion, linting, package recognition, all the good stuff.
... and in the free Visual Studio Code, also crossplatform like IntelliJ.
(Nothing against IntelliJ, JetBrains is a cool company with amazing products IMO, I just prefer VSCode myself.)
Yes, this makes writing Typescript actually faster for me than vanilla JS. The code completion in IntelliJ is really amazing.
And, eventually, you'll be lulled into properly typing your variables, because damn it those type annotations are super handy! Oh, and you start defining your own interfaces because it makes your code more readable and easier to reason about. Oh, and...etc etc etc.
If you start by gradually adopting the extra features that TS provides, you'll never want to go back.
this is such a bad excuse, imo - the "work" required to get your wheels off the ground in a JS -> TS transition is literally just changing the filename. 99% of syntax / TS errors that present themselves after that change are bugs that your code already had.
I can't even imagine writing JS without these annotations esp given all the odd behaviors of the language.
But now js is adding classes and there’s typescript , now it’s like Java all over again.
Welcome to programming, where the fads go back and forth in cycles.
I also read a commment here saying people love React because now they can add code all over their view file.
Well adding code in view file is ugly and there’s a reason why we tried to get away from doing that.
Sounds to me people want to just go back to the 90s-00s!
Wow! Bye bye Javascript! Such arrogance! In a way it always feels like Typescript developers are way beyond all those poor suckers still coding in Python, Javascript, Ruby, Coffeescript, etc.. Dynamically typed languages are DANGEROUS!!! just as C is DANGEROUS!!! I'm so happy C is still being used and not abandoned in favor of C# or so.
I know I can be way more popular preaching Typescript nowadays, it would make me really cool, smart and up to date. Not going for Typescript proves I'm mediocre at best. This is not cynic, this is real when I talk to fellow web developers.
I believe static type checking should ideally be done by the IDE, we shouldn't need an entire new language for that with all its shortcomings, issues and whatsoever. And we'll see what's left when the hype is over and the next big thing in the Javascript world comes around. At least heaps of Typescript code bases that need to be rewritten.
I am not sure what you are trying to say here. Javascript is barely typed, so presumably you do need a new language to perform type checking.
Unless you mean that your IDE should be able to infer types, which is unlikely because typing defines intent, and we've all read plenty of code where we can't figure out what the code author intended.
The main point you're missing is that TypeScript is gradual. It's a superset of JavaScript, meaning that you can use TypeScript when you want it or ignore it when you don't want it.
Any valid JavaScript file is also a valid TypeScript file.
> Dynamically typed languages are DANGEROUS!!!
See above. TypeScript is dynamically typed by default. It just also has a static type checker that you can opt into.
A good practice in both JavaScript and TypeScript is to use "const" instead of "var" or "let" anyway.
> I believe static type checking should ideally be done by the IDE
JavaScript doesn't have enough explicit information for this to be possible. The IDE can do a lot, but it can't do nearly as much if the developer's intentions are implicit. In TypeScript, the developer has the option (again, not the requirement) to make her intentions explicit.
> we shouldn't need an entire new language for that with all its shortcomings
Again, see above. TypeScript is a superset of JavaScript, not an entirely new language.
> At least heaps of Typescript code bases that need to be rewritten.
No, they won't. TypeScript compilers will still be available, even if they're not actively developed. They produce JavaScript, so worst-case scenario, you'll just have a JavaScript code base.
If you only have a single file that has a limited set of responsibilities (e.g., just a small tool) and is not shared / distributed then JS may still be okay, otherwise always go for TypeScript.
Honestly, TypeScript never slows me down. The enhanced completion / IDE knowledge, compile-time type checking, and transpilation supporting newer ES features + React is a huge boost.
I.e. Elm, ReasonML/ocaml/bucklescript, Purescript, Haskell with ghcjs, Rust with web-assembly compilation, or even wasm from Go?
I am somebody who really likes to dabble, but is not a fronted person, so I am thinking, what would make you consider switching?
Now I work on a project with Elm for the frontend. It's not as easy to just jump into, so wouldn't use it for a project where lots of developers sometimes have to make additions. But once up to speed, Elm is great.
Typescript has significant advantages over vanilla JS, so that's why I use it. If those other options offered a significant improvement over Typescript AND said features were something I needed, I wouldn't hesitate to use it. For now, getting better at the languages I already know seems like a better use of time.
Would love to but TypeScript integrates so seamlessly with JavaScript the language, and the ecosystem, that it's difficult to justify using my preferred language (Scala/Scala.js).
Another thing to note is that working TypeScript with JavaScript feels idiomatic; I can't say that for any other typed-to-js language I've seen or worked with.
Finally, the ecosystem, this is the deal breaker. With TypeScript it's similar to languages on the JVM, you get a huge ecosystem for free. The alternative is manually writing wrappers/interfaces for JavaScript libraries, or, if you're lucky, interface generators are available, but even then there are often caveats/tweaks required to get things working as expected.
Once it's up and running TS is freaking amazing
http://seg.phault.net/blog/2017/10/typescript-without-transp...
does anyone know if there are limitations to this style?
https://github.com/Microsoft/TypeScript/wiki/JsDoc-support-i...
Do you have any concrete examples? I would argue that a complex project that is already in TS benefits more from remaining in TS, so I’m curious what your reasoning was.
2) Compile time overhead when iterating. For many visual development tasks it just takes too long to change something, compile and see results in the browser. Without TS it's 0 latency. With TS it was a few seconds. Without TS I could make 10 changes with 2 seconds latency, and perhaps I make 1 type mistake costing me 20 seconds of debug time, for a total of 40 seconds. With TS I make 10 changes with 5 seconds latency and zero mistakes but now total overhead if 50 seconds. I want to be able to get the documentation and static type checking benefits without giving up that instant feedback.
In general, contrary to what you'd expect, documentation options for ts seem poor. Typedocs is painful and ugly. I'll be trying DocFx next but it looks like it has a complicated workflow.
Also you should imo have some typescript experience to be able to write the jsdocs in a way that they work how you expect (e.g. typing a parameter as an object does not work how you would expect, it).
So next thing I thought was to just write js and accompany each file with a manual .d.ts definition. This works when you consume the exports of the file but it doesn't when you're within that file (see https://github.com/Microsoft/TypeScript/issues/30304) which makes it useless imo. Also even if it worked, now that I think about it, I'm again not sure how creating documentation for that would work.
In the end I've reluctantly settled for just switching completely to typescript and hopefully the documentation situation will improve when tsdoc gets farther along.
I say that half in jest but one of the dumbest & most destructive things in software engineering is tribalism or militant belief systems. Having a “neutral” way to deal with them (or something like “data” seeming like it’s neutral) once and for all does wonders for productivity
People who don’t like or trust typescript, what’s your good-faith case against it. Personal productivity or ugly/confusing syntax are valid reasons I think
It's hard for me to understand how anyone can't see the benefit of types at compile time. I mean if you write more than 100 LOC you are bound to make a mistake that a typing system would catch and think "I wonder if there is a way the my build process could catch such as silly error". I can only imagine that such programmers are "write only" in that they add lots of features but rarely need to come back and maintain their code. With an ide with autocomplete it may mitigate some mistakes and give a false sense of security about non-static typing.
Having strongly typed languages has much more to do with those developers who can't live without the autocomplete feature in their IDE. Especially the army of ASP.NET developers who are used to working with Visual Studio.
We'll probably be seeing more of those code generator patterns with TypeScript soon.
Not saying TypeScript is a bad thing. But strongly typed languages do come with their pitfalls. Massive amounts of code providing a simple CRUD interface. An entire team working on their complex microservice solution which is just wrapping some already existing API. Things that can easily be achieved with a couple of lines of cough PHP cough.
I've also found it easier for new developers to become productive sooner when the language and framework guides them in the direction they want to go. I'm all for anything that supports a positive developer experience, and it's more concise to express my design in terms of static types than it is through mounds of documentation or exploring the code.
The type system is a little bit more advanced than those languages with nominal type systems like Java. Sometimes typing old code is tricky. But the good news is you can escape by using any anytime.
I never expect something to work while I'm offline, or do they mean the cached contents like a page would work if it were just cached HTML and CSS? As for real-time notifications, I don't know anybody who uses those, the other day someone was telling me how pissed off they were that they always get them from some news site, and they didn't mean to enable them, now they can't find where to turn them off!
I still suspect that a large part of Typescript adoption comes from developers who are used to working in an IDE (Visual Studio, Eclipse, etc), and are uncomfortable with javascript's natural "textfile->compiler->test" workflow.
Its also worth noting that support for typescript (that isn't Microsoft marketing) tends to come from outwith established tech environments. Using Typescript outwith MS environments can be challenging.
As for GP's post, I can only hope that Typescript leaves a similar legacy, even if the language itself ceases to exist.
Try building an app in ES3 JavaScript (i.e. it has to support IE7 with no transpiling) if you haven’t before. It can be rather frustrating if you’re used to modern features.
Prototype.js was the kick that eventually led to the standard library updates in ES5, and (as another post has said) CoffeeScript was the kick that eventually led to language- and syntax-level updates in subsequent versions.
https://www.zdnet.com/article/who-built-microsoft-typescript...
I assume the correct link should be pointing to https://khalilstemmler.com/wiki/concrete-class/
The problem is really the Node ecosystem, which is a mess and a circus of security issues. Deno[1] may fix most of JS/Node's problems eventually.
For now, if you want a type system that's comparable to TS with a great ecosystem, the best you can get is either F# or Kotlin. If you're not a fan of ML, then you might want to start with Kotlin.
Can you elaborate on promise fatigue. In our setup we use async/await and the latest ES (although most of us prefer not using decorators if we don't have to - only used it with mobx for UI code) bundled through webpack.
I am not sure what you mean by poor eco-system either. Both JS and TS (increasingly), I feel like is the second best eco-system (in terms of libraries and tooling) after a mainstream language like Java.
There are some warts I think in JS (around numbers, stream handling in pipes), but I think very few languages don't, and it depends on how likely you would be fiddling in those areas.
Extra tax on build pipeline cannot be escaped for anything with any complexity.
I feel like anyone who can write a complex app in good js, refactoring along the way at good speed, and meeting deadlines along the way and not introducing too many regressions, and come back and maintain it after a 6 month context switch, either has way too much time on his hands and/or works too many hours, or is much more smarter than I am and has it figured out.
These are all, at the end of the day, XY-style problems whose suboptimal answers can snowball into unmaintainable code. I do think there is a serious lack of training material in this area, though.
I'm interested in learning more about this, any other keywords I should throw in if I start Googling about it?
Actually, async-await is a nice feature of the JS paradigm that simplifies things and that I miss in the threaded world of Java.
But yes - use TS where you would have used JS, but TS up against other, more classical languages may not be the right choice for a lot of non-web projects.
You can start this promise early, await two parallel DB request + one API request, branch off some more async work depending on a response, await that initial promise that had a head-start because now you actually need it, and then run 10 tasks with no more than 3 in-flight at a given moment. And the code would look exactly how I just described it. And it's ubiquitous.
It's a good tool in the belt and imo the most trivial async implementation out there.
In my own Hades moment -- Microsoft know how to build great developer tools. The developer ergonomics of TypeScript and Visual Studio Code are excellent. It's really quite surprising to see how far the JavaScript world has come.
Similarly, the DevOps world is becoming much (much) more JavaScript friendly. Serverless, Netlify, and things like AWS Amplify -- all with their foibles, but pushing JavaScript in meaningful ways. I don't think this is the case for many other languages/ecosystems.
So it makes absolute sense to write the backend in the same language...if you can. Typescript goes a long way in making this pleasant.
Nothing is stopping you from writing the backend in different language...but as an organization, you lose the advantage of shared thinking in one stack.
JavaScript actually handles async IO nicely, and I've never heard this term before (callback hell, yes). So promises and async/await sugar actually make it pretty nice.
> poor ecosystem
Ecosystem is fine, just don't jump on everything new. The well-known problem is standard library, it is indeed a problem, usually addressed with a mix of additional packages.
> For the extra tax you pay in terms of build pipeline
This pipeline is norm in the frontend development, so in case you have people proficient in tooling, it is not that high (of course, it is hard if you have Java developers).
> Typescript vs Javascript as if these are the only two possible options for web development
They are not, but the nice thing about it is that you can have almost the same tooling as on your frontend, if you have some sort of complex application. So that increases speed of development, and TypeScript gives you some sense of scalability.
Last part is that TypeScript can be adopted incrementally, while another languages will require complete rewrite (and different deployment, etc).
As you put it, "Once you get into the controllers they tend to become entirely procedural and effectively completely synchronous" but that's because for most people it's far easier to think about a problem as a series of individual steps. Even then, if you have a bunch of serial awaits it's usually pretty trivial to go back and then await on a Promise.all() if you realize things can be run concurrently.
I see this over and over.
"Thing thing = await loadThing(); ": Start loading thing, then await so the main thread can continue doing other stuff while thing is loaded.
"Thing other = await thing.doSomething();": Invoke some other function that does some stuff in the background, then await so the main thread can continue doing other stuff while thing does something.
"thing = await saveThing();": Saving can often be done in parallel so no need to block the App while something is saved. Invoke the save function, then await so the main thread can continue doing other stuff while thing is saved.
This gets you the advantages of async programming while still maintaining a legible coding style that looks similar to common synchronous code.
You sound like we can be innovative on how we code but how can steps that do
- Parse inputs
- Query database
- Return output
be asynchronous?
Unless I'm writing a background job that is decoupled from HTTP requests, everything after the router is synchronous.
Elm, bucklescript, Scala.js to name a few.
```
(defn simple-component []
[:div
[:p "I am a component!"]
[:p.someclass
"I have " [:strong "bold"]
[:span {:style {:color "red"}} " and red "] "text."]])
```I'd argue that buying into a more comprehensive language would probably be worth it if everyone was into it, but in my own experience JS devs really, really like JS and have very little interest in learning something better suited to the problem they're solving. This is true outside of software too. People love familiar tools and methods, even if they aren't the best ones.
There's also the fact that your team's output will be best based on their enjoyment and engagement. Even if the tooling isn't perfect, they'll probably build better software if they're enjoying it. JS and TS are good enough in most cases to get teams where they need to be.
Thanks for reading!
I would suggest definitely not using Vanilla JS. There are excellent type systems over JS, TypeScript, Scala.JS, BuckleScript to name a few each with their pros and cons (I don't know what cons BuckleScript has though, maybe relatively smaller lib-ecosystem)
Lots of the perl6 code I've seen types most stuff at least at interface boundaries.
The biggest con is definitely the smaller lib-ecosystem, creating bindings for functions is generally relatively painless but if you're using javascript libraries with large API surfaces that will be a large time sink. Otherwise it's been a joy to use.