deniz.in

Markets

Weather

Loading weather

· via Cloudflare blog

Cloudflare rewrites workerd module registry for Node.js-style module resolution

Cloudflare has rewritten the module registry in workerd so Workers resolves modules as real URLs, supports import.meta, compiles lazily and shares caches across isolates, available behind the new_module_registry flag.

Cloudflare rewrites workerd module registry for Node.js-style module resolution

What Cloudflare rebuilt

Cloudflare has rewritten the module registry inside workerd, the open-source engine that powers its Workers runtime, so that it handles module resolution, loading and caching the way Node.js does. According to the Cloudflare blog, the new implementation is quicker, tracks web standards more closely, and is available now behind an opt-in switch: adding new_module_registry to a Worker's compatibility_flags turns it on. Existing deployments continue on the current registry, which Cloudflare says remains in place.

The rewrite caps a broader compatibility push. Cloudflare says the Workers runtime now covers every stable Node.js API that makes sense in a serverless context, with those APIs on by default, and that Node.js applications up to 64 MiB can be deployed on all plans now that the compressed bundle size limit has been dropped.

API surface alone was not enough, Cloudflare argues, because Node.js programs also depend on how a runtime resolves and caches ESM, CommonJS and WebAssembly modules — precisely the responsibilities of the module registry.

What the flag unlocks

Enabling new_module_registry brings a list of behavioural fixes. import.meta.url, import.meta.main and import.meta.resolve() all function. Module specifiers are parsed and resolved as genuine URLs, query strings and fragments included, and node: built-ins map to a single module instance regardless of how they are referenced. Import attributes such as { type: '' } are validated properly, require() against an ES module follows Node.js require(esm) rules, and errors are raised through consistent classes and messages whichever path loads the module. Modules compile lazily the first time they are imported, statically or dynamically, and WebAssembly modules gain support for source-phase imports.

Bundlers can now do less

Today, most Workers code arrives pre-bundled. Cloudflare explains that Wrangler runs esbuild to fold an application and its dependencies into a single module script, replacing imports with ordinary function calls — scripts the company has seen grow to hundreds of thousands of lines. The Cloudflare Vite plugin takes a different route: with Vite 8, Rolldown handles bundling, resolving imports, converting CommonJS to ESM where needed and emitting an entry module plus any chunks produced by code splitting.

Node.js APIs are not polyfills in this model; they are built into workerd itself, and Wasm, text and binary modules are uploaded as separate files referenced by specifier. Deploying with --no-bundle sends the full module graph to the runtime exactly as written. The new registry, Cloudflare says, makes it possible for bundlers such as Rolldown to apply fewer transformations and delegate module resolution to the runtime instead.

Why the old registry held things back

The previous implementation resolved specifiers as filesystem-style paths rather than URLs. That seemingly small distinction, Cloudflare explains, ruled out import.meta.url, made relative imports follow different rules than new URL(), and forced node: and cloudflare: to be handled through bespoke string prefixes rather than as actual protocols. It also compiled the whole Worker bundle up front whether or not every module was used, and kept a private copy of everything in each V8 isolate — a real cost, since Cloudflare runs several V8 isolates of the same Worker across CPU cores to distribute load, meaning identical source was compiled and held in memory repeatedly. None of this counted as a bug, but it made the implementation hard to evolve without breaking changes; the new registry starts from URLs and treats lazy compilation and cache sharing as design foundations.

Cloudflare has also added reference documentation to the workerd repository covering how the new registry interacts with V8's module APIs.

URL semantics in practice

A few behaviours are worth knowing. import.meta.main is true only for the module configured as the Worker's entrypoint; every other module gets false. import.meta.resolve() is a pure string operation, as in Node.js and browsers: it does not verify that the target module exists, throws a TypeError for specifiers that cannot be parsed as URLs at all, and normalizes percent-encoding the way new URL() does — collapsing dot segments without decoding characters that were already encoded. Because specifiers are now URLs, different query strings or fragments pointing at the same source are treated as distinct module instances, mirroring the module-identity rules browsers use.

Why it matters

For Workers developers, this closes a category of failures where code or dependencies simply broke on Workers — import.meta.resolve() previously did not exist in the runtime. Lazy compilation and caches shared across isolates should reduce startup work and memory overhead for larger apps, which matters as Cloudflare courts Node.js projects up to 64 MiB. It also shifts responsibility from build time to the runtime, letting tools like Rolldown skip transformations and narrowing the gap between what bundlers produce and what Node.js itself would do. Because the change ships behind a compatibility flag, teams can opt in Worker by Worker rather than being forced into a migration.

  • #cloudflare-workers
  • #node-js
  • #serverless
  • #javascript
  • #module-resolution