Before the DOM APIs converged, the same operation needed different code per browser. That is the whole reason a compatibility layer was worth loading.

A wall of patch panels and looped network cable in a lit equipment room, straight on
The libraries existed because nothing agreed Before the DOM APIs converged, the same operation needed different code per browser. That is the whole reason a compatibility layer was worth loading.

Four browsers, four answers to the same question

Before jQuery, before Prototype, before any of the compatibility libraries that became load-bearing parts of the web, there was a simpler problem: the same operation produced different results in different browsers, and nobody had solved it at the level that would have mattered.

The Document Object Model — the tree of nodes that a browser builds from HTML and exposes to JavaScript — had a specification, in principle, going back to the late 1990s. The W3C published DOM Level 1 in 1998, Level 2 in 2000. What it could not do was make Microsoft, Netscape, and their successors implement it the same way. The spec was a document. The browsers were products, each carrying years of accumulated decisions made before the spec existed, each with commercial reasons to do things slightly differently, and none of them bound by anything stronger than a desire to be seen as compliant.

The result was a landscape where attaching an event listener required checking which method the browser supported. Internet Explorer used attachEvent; the standards-track path used addEventListener. Getting the element that triggered an event meant event.srcElement in IE and event.target everywhere else. Retrieving an element by ID was reliable; almost anything beyond that was not. DOM Level 2 Events defined the model clearly enough. What it did not do was retroactively fix the IE codebase, which had diverged well before the specification was finished.

Lifted from the piece

Chronology

  1. 1995JavaScript designed in ten days at Netscape; ships in Netscape Navigator
  2. 1998W3C publishes DOM Level 1
  3. 2000W3C publishes DOM Level 2, including Events specification
  4. 2005Prototype.js released
  5. 2006jQuery released by John Resig (January)
  6. 2009ES5 specification published; Remy Sharp coins "polyfill"
  7. 2009querySelectorAll widely available
  8. 2015ES2015 (ES6) published; native module, class, arrow function, Promise
  9. 2016left-pad incident; npm supply-chain fragility exposed
Two identical doors side by side in a bare corridor, one propped open
Two rivals lost to the one with better documentation Prototype and MooTools were technically comparable and both faded. The difference was how easy each was to learn from its own site.

The cost of writing it twice

The developer's practical answer was conditionals. A project large enough to care about cross-browser behaviour would accumulate a library of internal utilities — functions that checked what the environment supported, branched accordingly, and presented a single interface to the rest of the code. Every team with a serious enough front-end wrote some version of this. The functions overlapped, were subtly incompatible with each other, and could not be shared because they lived inside proprietary codebases.

The public libraries that emerged from roughly 2005 onward were, at bottom, that internal utility collection made portable. Prototype.js appeared in 2005, extending native objects directly. MooTools followed a similar philosophy. jQuery, released by John Resig in January 2006, took a different path — a wrapper object rather than patching the global environment — but was solving the same core problem. The dollar-sign function abstracted away the inconsistency so that a developer could select elements, attach events, and traverse the DOM without knowing which browser would be running the code.

That abstraction was worth a network request and the parse time of a file measured in tens of kilobytes because the alternative was writing and maintaining the detection logic yourself, in every project, forever. The library was a tax on bandwidth paid in exchange for not having to think about attachEvent again. The economics made sense precisely because browser disagreement was so durable. If IE and Firefox had agreed on event handling in 2004, most of the motivation for jQuery disappears. They did not agree, and jQuery became the most widely deployed JavaScript library in the history of the web, appearing on a majority of measured pages for years after its initial release.

Lifted from the piece

The disagreement in concrete terms

  • Event listenersaddEventListener (standard) vs attachEvent (IE)
  • Event targetevent.target (standard) vs event.srcElement (IE)
  • XMLHttpRequestActiveX object in IE; later standardised from that base
  • Element retrieval beyond IDeach engine handled traversal differently until Selectors API

The specific shape of that disagreement is worth naming. It was not always that one browser was wrong and another right. Sometimes both implementations predated the standard and the standard had later chosen between them, or modified both, or invented something new. XMLHttpRequest — the mechanism behind what would later be called Ajax — was originally an ActiveX component in Internet Explorer before it became a standard API. The standard eventually absorbed the IE behaviour and codified it; by then the conditional code checking for the IE version was everywhere and would stay everywhere for years.

A grid of small coloured tiles filling the frame edge to edge, some green some red, flat even light, shot straight on, no writing or numbers anywhere
A support table became the thing people argued from Can I Use turned browser support into a citable artefact, which changed technical arguments from opinion into a shared reference.

When the standard arrived late

The deeper pattern is that standards arrived after behaviour, not before it. Brendan Eich designed JavaScript in ten days at Netscape in 1995 under pressure to ship. The language went into browsers before anyone had written a formal specification for it. TC39, the committee that now governs ECMAScript, formalised what already existed and then, gradually, improved it — but the formalisation always chased the deployment. The DOM specifications followed the same logic. They described what browsers were mostly doing and tried to pull outliers toward a common line. The outliers did not always move quickly.

This is why compatibility tables became documents people argued from, and why sites like Can I Use accumulated genuine authority. Knowing whether a given feature was safe to use required consulting a record of what had actually shipped, not what the spec said. The spec said addEventListener was the right API. The question of whether you could use it without a fallback was a different question, answered by data rather than by the W3C.

The polyfill — a term coined by Remy Sharp around 2009, borrowed from the DIY filler used to patch walls — named the specific practice of loading JavaScript that implemented a standard API in browsers that lacked it. The word was useful because it distinguished this pattern from a shim (which might wrap a non-standard API) or a plain utility function. A polyfill promised that after it ran, the environment behaved as the standard said it should. That promise was valuable precisely because browser vendors were moving at different speeds and the standards body was setting a target they had not all reached.