A while back, I made a half-observation, half-joke on LinkedIn.
The great JavaScript toolchain rewrite barged in. The older tools that helped cement the language's place in our stacks became uncool, if not suspicious. Their sluggish performance took the blame for slowing down millions of lines of code on their way to production. It's increasingly difficult to keep track of the cool kids on the block. Their names might not be catchy, but their promise is captivating: speed, and a lot of it.
It's a strange time to be a JavaScript developer. The language has never been more ubiquitous, yet it seems to be losing ground in its own backyard. The tools are faster, the setup easier, the abstractions thicker. We write, configure and wait less. We bundle, ship and hotfix more. What happens between our code and the end product becomes easier to ignore.
The benchmarks are all beaming green. The line charts are trending up, down, or sideways—whichever direction means fewer calls at 3 AM. So why am I here to ruin the party?
The language that wouldn't die
For a language (in)famously written in ten days, JavaScript managed to achieve the unthinkable. It escaped its birthplace, the browser, and started swallowing everything around it. The reaction was predictable: Who in their right mind would write their server and tools in JavaScript? It turned out, many many many would. JavaScript made it onto phones, gaming consoles, microcontrollers and your fridge. If it understands bits, it can run JavaScript.
Despite its popularity, JavaScript never had it easy. Not long after its inception, attempts to fix, or rather replace it altogether, were already in motion. Microsoft endowed Internet Explorer with VBScript and JScript, their own JavaScript flavour—not to be confused with J-Pop and JRPGs. In the 90s, everything sounded cooler if it started with J. Macromedia, and later Adobe, littered the internet with Flash intros and games powered by ActionScript. Google bet on Dart for the future of Chrome before relegating it to Flutter.
Projects such as CoffeeScript added some syntactic sugar to the JavaScript cup. When we weren't trying to replace JavaScript, we were busy patching it from the outside. jQuery unified a web platform whose browsers were barely on speaking terms. Lodash filled glaring gaps in the language's arrays and objects. Moment.js made dates somebody else's problem.
JavaScript wasn't sitting still while everyone plotted its demise, either. Within its single thread, it was rather busy. Browser vendors, standards bodies and the community kept pushing the language forward. ECMAScript releases brought long awaited language features. TC39 kept the proposals coming, and browsers slowly learned to agree on what JavaScript was supposed to do. Over time, its standard library became less embarrassingly sparse.
Then came TypeScript, and JavaScript's head was finally on a silver platter. Or so it seemed. TypeScript succeeded where the others failed by accepting one inconvenient truth: JavaScript wasn't going anywhere.
You could improve it, hide it, compile to it or complain about it. You just couldn't get rid of it.
The superpower we're giving away
One language to rule them all is both a blessing and a curse. We spent decades talking about the curse. Somewhere along the way, we forgot about the blessing.
As JavaScript continued to spread, it started eating its own dog food. Node.js enabled a slew of tools to emerge and conquer the ecosystem. Linters, bundlers, formatters and test runners were speaking the same language as the code they linted, bundled, formatted and tested.
If, or rather when, something broke in that toolchain, the average JavaScript developer would be able to check the code. Maybe they'd understand the bug. If they'd had their eight hours of sleep, maybe they'd fix it. If they were feeling combative, maybe they'd submit a pull request. At the very least, they knew enough of the language to confidently blame the bug on the maintainers.
And that familiarity travelled surprisingly well. The same language followed developers from the browser to the server, and eventually almost everywhere in between. JavaScript accelerated the rise of full-stack engineers, or perhaps full-ecosystem engineers. Whether anyone can truly master both ends of the stack without achieving demigod status is a discussion for another day. But JavaScript made the transition considerably easier. Engineers could bring along their knowledge of the call stack, prototype-based inheritance and the unfortunate fact that typeof null === "object" to almost any project.
People built entire careers and businesses around this catch-all ecosystem. The fact that most of it was and continues to be open source and free certainly helps.
JavaScript won the browser wars, and several key battles elsewhere. Then Rust and friends showed up for the trophy and the commemorative photo.
In pursuit of milliseconds
Rust, Go and Zig are taking over increasingly large parts of the JavaScript toolchain. And there's an obvious reason for that.
They're fast. Really fast.
We're compiling JavaScript with Rust to produce JavaScript that runs inside an engine written mostly in C++.
Nobody wants to stare at a build process long enough to form an emotional attachment to the progress bar. But when the replacement for an already-fast tool advertises itself as ten times faster, I start wondering what we're supposed to do with all those precious milliseconds it just handed us back.
Take an extra sip of coffee?
More importantly, what did we trade for that speed?
Rewrite a bundler in Rust and you haven't only made it faster. You've also shrunk the pool of JavaScript developers who can maintain it. The new tool still looks like a duck and quacks like a duck, but it's a different beast altogether. Its internals retreat behind a black box that fewer people hold the keys to. The source may still be open, but the door to contributions is closing.
Maybe that's a perfectly reasonable trade-off. But it is a trade-off. And we don't seem particularly interested in that side of the benchmark.
Not everything that shines is gold
Sometimes it's rusty.
Software engineering has always suffered from a particularly acute case of shiny object syndrome. Languages have their moment. Frameworks become fashionable. A few successful projects establish a pattern, companies invest in it, conference talks follow, laptop stickers get handed out and suddenly a technical decision becomes the main selling point.
"Written in Rust" starts sounding less like an implementation detail and more like a feature.
Success breeds imitation. One tool gets rewritten and becomes dramatically faster. Another follows, and another. Peer pressure mounts. Soon enough, being written in JavaScript starts to look less like the obvious choice for JavaScript tooling and more like a losing bet.
We're laying increasingly faster tracks for a steam train that likes to take its time.
None of this means those compiled languages are the wrong tools for the job. Quite often, they are exactly the right ones. But there's a difference between the right tool for this job and the right tool for every job that looks vaguely similar to whatever the competition is doing.
Once the shiny new tool also happens to top the benchmarks, resisting it becomes considerably harder. Speed provides the technical argument. Trendiness takes care of the rest.
Destination unknown
Our pursuit of faster JavaScript has taken us to a rather peculiar place.
We're compiling JavaScript with Rust to produce JavaScript that runs inside an engine written mostly in C++.
Perhaps that's the natural evolution of mature ecosystems, and JavaScript doesn't need to swallow the entire stack to remain relevant. Languages can coexist and still thrive. The web was built on at least three of them, before many more joined in.
Taken individually, every step makes perfect sense. Taken together, they point somewhere more interesting. We're putting a substantial amount of engineering into optimising everything around JavaScript while JavaScript itself remains the destination. We're laying increasingly faster tracks for a steam train that likes to take its time.
The thing about speed is that it doesn't always guarantee a smooth journey when the train itself isn't designed to keep up. Sooner or later, faster tracks will make less and less of an impact. And someone, much smarter than me, will have to ask the difficult question: how far can we go before rebuilding the web from the ground up starts looking like the saner option?