Motivation
Reactivity arrived in the browser as something you bolt onto HTML with JavaScript. A framework takes the page over, the markup becomes its output rather than the document, and every page pays for a runtime, a build step and a mental model before it can react to anything.
I kept wondering what the other order would look like — reactivity in the markup itself, so that a page stays a page and gains the two things a static document cannot do for itself: recurring markup becoming a tag you name once, and the page keeping itself in step with its own data.
What the 2000s got right
The idea is not mine. OpenLaszlo had it in the 2000s, and the part worth inheriting was not its syntax or its runtime — both of which belong to their decade — but what an attribute was allowed to mean. An attribute stated a relationship, and the relationship stayed true. You did not write what to do when something changed; you wrote what was the case, and the framework's job was to keep it so.
That is a smaller idea than a framework and a larger one than a
feature. It says a property is not a box holding whatever was last
put in it — the assumption plain JavaScript entails and almost
everything since has kept, hedged with a
signal(), a
ref() or a dependency array to
re-introduce by hand the reactivity the assumption removed.
For me, it was a revelation. I used it with great pleasure on several projects, but it was already on its way out.
The JavaScript frameworks that followed, from React onwards, went the other way. They got a great deal right — components, one-way data flow, an ecosystem — but I think threw out the baby with the bathwater. I waited a long time for someone to bring that vision up to date. Nobody did.
What I kept trying in the meantime
Not patiently, and not only lately. The first attempts were not attempts at something new at all: OpenLaszlo's HTML output was far too heavy for the iPhone, so I patched the thing itself — passive replicated items, and whatever else it turned out to be missing — in the JavaScript on the page. Frameworks of my own came at the turn of the decade, each one deliberately smaller than the idea.
The serious attempt came in the early 2010s, written in Haxe, and it reached something the decade had no name for yet: one source rendering on Java, PHP and Node alike. It wanted optimisation it never got and stayed a research project. But isomorphism stopped being an aspiration for me there and became a requirement, which is why it is in this one rather than bolted on later.
Then React arrived and the industry went the other way. I used it
professionally for years, which is the main reason I trust my own
opinion of it: not that it is bad, but that it is the wrong way
round. The machinery is enormous for what it delivers, and the cause
is the choice underneath it. A JavaScript variable is a box holding
whatever was last put in it; a reactive value is a relationship that
stays true. Making the first impersonate the second is what a
signal() or a dependency array is
for — and then the result has to be taught about markup
afterwards.
Once that mismatch was clear the direction stopped being a choice. Rather than make JavaScript reactive and markup-aware, make the markup reactive and give it a notation for values. The markup already had the harder half.
The last push
Svelte almost saved me the trouble. It arrived doing what I had stopped expecting anyone to do — a compiler rather than a runtime, reactivity without a virtual DOM, the markup back at the centre — and for a while I assumed the problem was solved and I could put the whole question down. Then it did what the others do. Stores became runes, and working code became a migration. That churn is not bad luck: it is what happens when a tool's vocabulary is its own invention, because an invention can always be reconsidered.
By the mid 2020s I had had enough of waiting for somebody else. This time I set out to build the whole of it rather than the interesting part — the language, a runtime worth deploying, the first kits, a CLI that is also a server and a compiler, an editor extension — because what had stopped every earlier attempt was never the idea. It was everything around the idea. Mostly, though, I wanted to put a low-grade obsession to rest.
What is different now
Everything that dated OpenLaszlo is gone, and none of it was the idea. There is no plugin, because the browser is the runtime it needed. There is no proprietary player and no XML dialect to learn, because the platform grew into the job: HTML is the syntax, the DOM is the scope chain, and JavaScript is the expression language. Nothing here is a language of its own.
The scope chain is the part people find surprising, and it is the
one that needs no invention at all. A value declared on an element
is visible to everything inside it, because that is what nesting
already means — so there is no
provide/inject,
no Context, and nothing to wire.
The document tree was a scoping model the whole time.
And because the whole of it compiles ahead of the browser, the same page renders on the server and comes alive in the browser from the same source — which is the third thing the original vision wanted and the decade could not give it.
What it should add up to
What that buys, at the top of an app, is a page written in its own
nouns. <product-cart> rather than
the markup a cart happens to be made of, and the constraints between
its parts stated as rules —
:total=${price * (1 - discount)} —
rather than as the code that has to run when either one changes.
A main page that reads that way is a map of the application: what it is made of and what has to stay true of it, with every detail of how delegated to the fragments it imports. The nouns are yours, and that is the point — the language gives you naming and rules, not a vocabulary of its own.
What it is not
It is not an application framework, and the distance matters. Markout is the presentation layer: the DOM is the view, your data is the model, and Markout is the logic between them. Anything framework-shaped — a datasource, a router, a theme switcher — is written in Markout as a kit rather than built into the language, which is what lets the language stay a handful of rules.