← All posts

Eleven years of JavaScript, from jQuery to React Native

Gjorge Karakabakov

My skill list has jQuery UI and Astro on it. Those are seventeen years apart, and both of them paid for dinner.

I have been writing JavaScript professionally since 2012. Long enough to have shipped production code in CoffeeScript, to have argued about AMD modules, and to have watched three separate generations of tooling declare the previous one obsolete. Here is what each era actually solved — and the small number of lessons that outlived it.

jQuery, and the problem it really fixed

It is fashionable to be condescending about jQuery. It should not be. jQuery existed because the DOM API was genuinely hostile and browsers genuinely disagreed with each other, and it fixed both so thoroughly that it fixed itself out of a job. querySelectorAll is jQuery’s selector engine, standardised. fetch is $.ajax, standardised.

What jQuery did not have was a way to answer what is on screen right now. State lived in the DOM. You did not have a user object with an isLoggedIn flag; you had a .logged-in class on body, and the truth was wherever the last handler left it. Every jQuery codebase past a certain size fails in the same way: two handlers disagree about the current state, and there is no single place to look.

The lesson that survived: the DOM is an output, not a database. Everything since — Backbone, Angular, React — is a different answer to that one sentence.

Backbone, Require.js, CoffeeScript

Backbone was the first widely-used answer: put the state in a model, let the view listen. It was right about the diagnosis and left the hardest part to you — when a model changed, you decided what to re-render, and getting that wrong meant either a stale screen or a full repaint.

Require.js solved modules before the language had them. AMD’s ceremony looks absurd now, but the alternative was thirty script tags in a load-bearing order.

CoffeeScript is the one I am most nostalgic about and least sad to see go. It was a bet that JavaScript’s syntax was the problem, and it won its argument so completely that the language absorbed the wins — arrow functions, classes, destructuring, template strings, for...of. Every one of those was CoffeeScript first.

The lesson that survived: a tool that fixes a language gap has a shelf life ending the day the language closes the gap. That is not failure; it is the mechanism by which languages improve. Worth remembering the next time something feels permanent.

AngularJS to Angular: the rewrite that was correct

AngularJS gave you two-way binding and a digest cycle, and for a while it felt like magic. The bill arrived as $scope. Nobody could say with confidence where a value came from, and performance work meant reasoning about how many times a dirty-check would run.

Angular 2 broke everything, and it was right to. Two-way binding by default was the mistake; one-way data flow with explicit outputs is the correction, and it is the same correction React made from the other direction.

The lesson that survived: implicit magic scales badly. Any framework feature that makes it hard to answer “why did this change?” will eventually cost more than it saved.

React, and why it stuck

React’s actual contribution was not the virtual DOM — that is an implementation detail, and one several frameworks have since discarded. It was UI = f(state): describe what the screen should look like for a given state and stop writing transitions between states by hand.

Every bug class from the jQuery era — stale screens, two handlers disagreeing, the DOM as source of truth — is defined out of existence by that model rather than fixed. That is why it won, and why hooks, signals, and every “React but faster” project keep the same core idea.

React Native, and the caveat

At Yoco I built core features of a flagship mobile app in React Native and led the team doing it. It delivers on the promise: one codebase, real native components, and a web engineer can be productive in it quickly.

The caveat is worth stating plainly, because it is where teams get hurt. React Native is a mobile project. The parts that consume your time are the parts a web background did not prepare you for: list virtualisation with real data, memory on a four-year-old Android device, navigation state that survives the OS killing your process, the store review cycle. Being able to write the components is maybe a third of shipping the app.

The lesson: “you already know React” gets you through the door and about a third of the way across the room.

The build chain: Grunt, Gulp, Webpack, Vite

I have written config for all of them.

Grunt was tasks as configuration. Gulp was tasks as streams — genuinely nicer, and it lost anyway, because the problem moved. Webpack won by understanding the actual question: not “run these tasks” but “here is a module graph, produce bundles”. Everything since — Rollup, esbuild, Vite — is that idea, faster.

The lesson: the fastest build is the one that does not run. The most valuable thing modern tooling did was make dev builds incremental and native-ESM. Bundle size discipline came second, and it came from the same place.

What actually transferred

Eleven years, seven or eight complete rewrites of the ecosystem. What actually carried over:

  • The DOM is an output. Every framework since 2010 is a different answer to that.
  • Explicit beats implicit, every time, at every layer.
  • A tool that patches a language gap dies when the gap closes. Prefer the ones that patch a problem.
  • The abstraction you build will be wrong in a way you cannot predict. Which is an argument for small, replaceable pieces, not for cleverness.
  • Fundamentals compound and frameworks do not. HTTP, the event loop, the box model, how a browser paints. Every hour there paid off across all eleven years; every hour on a specific framework’s API paid off for about three.

I do not think the churn is as bad as it is made out to be. Most of it has been the same handful of ideas arriving with better ergonomics. If you learned why $scope was painful, you already understood why prop drilling is painful, and you will understand whatever replaces signals.

The syntax is what changes. The reasons are remarkably stable.