Upgrading JS dependencies is a nightmare. That includes build/dev dependencies.
Part of my job is to maintain more than 50 web projects. That includes streamlining, refactoring, upgrading, optimization etc. Some of them are a decade old.
The most painful and time consuming part is dealing with JS issues.
To anyone reading this: cut the fat and do it early. A little bit of perceived “DX” is not worth writing “npm install”.
Just write the code that needs to be written in a straight forward manner. Lean on standards and don’t get distracted by fads. Prioritize stability.
Me too, but it's usually because of the weird way TypeScript was built as a thin veneer over JavaScript. Unlike a proper strongly-type, statically-compiled language with a consistent underlying framework where unsound behavior is adversarially sought out and eliminated in the very design of the language from the start, TypeScript started off as just bolting types on to JavaScript and progressed to actually sanity checking the code that a JavaScript runtime happily accepts.
I just ran into a case yesterday where some code ported from JavaScript to TypeScript five or more years ago once more wouldn't compile after upgrading from TypeScript 5.1.x to 5.2.x and in this case it was a DOM event handler that for some reason (probably copy-and-paste from another location in the codebase) had a `return foo;` in a couple of the conditional statements in the event handler callback. Now obviously event handlers don't return anything, and anything they do return is ignored (perfectly normal for JavaScript). TypeScript didn't error out on that, but it did error out because not all the code paths returned a value, so it was no longer able to infer the return type.
A function (that returns a value) that can execute along some logic path without returning a value is a soundness issue. Granted it didn't actually cause any problems (as the return value was always ignored in all cases, the solution was to just remove the `return` statements that never should have been there) but tsc incidentally caught something funky that never should have been in the code in the first place.
So if you view incremental tsc releases as "tightening down all the bolts" then I think its behavior makes sense. I wish tsc caught everything from the start, but I'm only ever happy to see that my code no longer compiles because it means that something tsc *wrongly* accepted before is now being caught - which usually means that potentially errant runtime behavior has been identified and this is a chance for me to correct it. Which is, after all, the whole point of turning JavaScript into a compiled language - so I can catch errors (or at least as many as the TypeScript language is empowered to and the TypeScript compiler is smart enough to) at compile-time rather than at run-time.
(But I'm not primarily a web developer, I hate loosely-typed languages, I absolutely abhor runtime errors, and I think any soundness holes in a language should be treated alarm bells left and right, so perhaps my opinion is not actually reflective of the average webdev.)
This is usually the Typescript team tightening up some "loose" behavior that should have had saner defaults. For instance, the catch block default typing changed from unknown from any.
The bigger issue I've found is when you're using 3rd party type definitions for some library you can't always trust them to be correct or updated. It's a major risk for the soundness of your build.
I've had trouble where the build fails with various TS#### error messages, most of the time there was some change in Typescript syntax. I guess if you use those bounds you have less of those problems.