Native ES modules quietly removed the reason bundlers existed in the first place — and development tooling has been lighter ever since.
How bundling became load-bearing infrastructure
For most of the 2010s, the front-end build step only grew. Concatenation came first, because HTTP/1.1 made each separate file a round trip with real cost. Then bundlers added module resolution, tree-shaking, code splitting, and minification, until the toolchain between a developer's editor and a running browser was a substantial piece of infrastructure in its own right. webpack, released in 2012 and dominant by the middle of the decade, turned a text file into a dependency graph, resolved it, and emitted a single bundle that the browser could consume without knowing anything about where the code had come from. The browser's own module system was, for practical purposes, irrelevant — because browsers did not have one yet.
The specification that changed this was finalized as part of ECMAScript 2015, commonly called ES6. The import and export syntax introduced a standard module format into the language itself, after years of competing conventions — CommonJS in Node.js, AMD in RequireJS, UMD as an awkward bridge between them. TC39, the committee that maintains the ECMAScript standard, had been working through the design questions since at least 2013: how should modules be loaded, how should circular dependencies be handled, when should evaluation happen. The resulting semantics were static — import declarations cannot move inside conditions — which was unfamiliar to JavaScript developers accustomed to dynamic require(), but it made the dependency graph statically analyzable, which is what made tree-shaking tractable.
Browser vendors shipped native ES module support progressively between 2017 and 2018. Firefox, Chrome, Safari, and Edge all added it within roughly a year of each other, and MDN's compatibility data recorded the spread. What <script type="module"> gave a browser was real: it could fetch, parse, and execute a module graph itself, following import paths across the network, without any bundler having pre-processed the files. In production, with hundreds of modules and an HTTP/1.1 connection, that many round trips was still slow. But in development, on localhost, it was fast enough to be interesting.
Chronology
- 2012webpack released
- 2015ES2015 (ES6) finalizes
import/exportsyntax - 2017–2018Major browsers ship native ES module support
- 2019Snowpack released; dev-time bundling begins to look optional
- 2020Vite released by Evan You
The tool that noticed first
Snowpack, released in 2019, was built around the observation that the browser's native loader was perfectly adequate for development. Rather than rebundling on every change, it served files as ES modules, letting the browser handle the graph. A file change meant re-serving one file; the browser fetched only what changed. The rebuild cost went from seconds to near-zero, because there was no rebuild.
Vite, created by Evan You and first released in 2020, took the same premise and refined it. You had already built Vue.js and understood the friction of heavy toolchains; Vite used native ES modules for development while using Rollup for optimized production bundles, which meant the two environments had different characteristics but the development path was genuinely fast. Adoption was quick, partly because Vite's scope was clear and partly because the experience of starting a development server in milliseconds rather than tens of seconds was immediately legible as an improvement.
The mechanism that made both tools viable was the same: browsers had stopped being passive consumers of pre-processed bundles and had become capable of loading module graphs directly. This did not eliminate bundlers from production — the case for optimized, cache-friendly, minimized output at the network edge remained solid — but it decoupled production optimization from development experience. Those had been the same step for most of a decade, and separating them let tooling authors make different trade-offs in each direction.
What the shift actually changed
- Development servers: rebuild on change collapsed from seconds toward zero
- Production builds: still bundled and optimized, but now a separate concern
- HTTP/2 multiplexing: weakened the original argument for concatenating everything
- Dependency graphs: static
importsyntax made them analyzable without execution
HTTP/2's multiplexing also changed the calculus for production. Where HTTP/1.1 imposed a hard cost on each additional file, HTTP/2 could serve many small files over a single connection without the same penalty, which made unbundled or lightly bundled production deployments more defensible than they had been when the bundler was invented. The case for concatenating everything into one file was an argument about HTTP/1.1 latency, and that argument weakened as the protocol changed. The build step did not vanish, but its justification narrowed, and the browser's own understanding of modules did most of the narrowing.