Cloudflare Workers Module Registry Rebuilt: 64 MiB Bundles, Lazy Compile, require(esm)
Cloudflare rewrote the module registry inside workerd, the open-source Workers runtime, so it resolves specifiers as real URLs instead of filesystem paths. Alongside it, Node.js stable APIs are now enabled by default, and the bundle ceiling moved to 64 MiB uncompressed on every plan — the compressed size limit is gone entirely. Nothing existing breaks: current Workers keep the old registry, the new one is opt-in via new_module_registry.
What changed
Enable the flag and you get import.meta.url, import.meta.main, and import.meta.resolve() — the last one previously just failed, which is why any dependency touching it broke on Workers regardless of API shims. Specifiers are parsed as URLs: query strings and fragments create genuinely separate module instances (same browser semantics, so ./counter.js?a and ./counter.js?b each get their own top-level state). require() on an ES module now follows Node's require(esm) rules, with { type: 'json' } import attributes are validated instead of silently ignored, and node: built-ins resolve to one instance no matter the import path.
The structural change is laziness plus cache sharing. The old registry compiled the entire bundle up front and kept a private copy per V8 isolate — and Cloudflare runs multiple isolate replicas per Worker to spread across cores, so the same source got compiled repeatedly and held in memory several times over. The new registry compiles modules on first import and shares across replicas.
Why a builder cares
Two verified numbers make the tension obvious: Workers now allow a 64 MiB uncompressed bundle, but the runtime still enforces a 1-second startup time (isolate memory stays 128 MB). Eager compilation of a 64 MiB bundle inside that window was never going to survive contact with a real codebase — lazy compilation is what makes the new limit usable rather than theoretical. I checked the current limits doc: only uncompressed size counts now, with "no compressed size limit" stated outright.
Second-order effect: bundlers can relax. If the runtime resolves and caches properly, --no-bundle deployments and Vite 8's Rolldown output (a real module graph, not one flattened 100k-line file) stop needing esbuild to pre-flatten everything. The trap to watch: the query-string-is-a-different-module rule means any dependency doing cache-busting via ?v= specifiers will silently fork module state. That's spec-correct behavior that most Node code has never been exercised against.