From 80143307ac612994485057c85db434fba5f6b1f0 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 5 Aug 2026 09:59:53 -0700 Subject: [PATCH 1/4] Workers new module registry documentation --- .../2026-08-05-new-module-registry.mdx | 25 ++ .../new-module-registry.md | 29 ++ .../workers/reference/module-registry.mdx | 249 ++++++++++++++++++ .../runtime-apis/webassembly/javascript.mdx | 20 ++ 4 files changed, 323 insertions(+) create mode 100644 src/content/changelog/workers/2026-08-05-new-module-registry.mdx create mode 100644 src/content/compatibility-flags/new-module-registry.md create mode 100644 src/content/docs/workers/reference/module-registry.mdx diff --git a/src/content/changelog/workers/2026-08-05-new-module-registry.mdx b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx new file mode 100644 index 00000000000..750cdd21f1e --- /dev/null +++ b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx @@ -0,0 +1,25 @@ +--- +title: New module registry with import.meta, lazy compilation, and URL-based resolution +description: A new module registry implementation is available via the new_module_registry compatibility flag, bringing import.meta support, URL-based specifier resolution, lazy compilation, shared caching, and WebAssembly source phase imports. +products: + - workers +date: 2026-08-05 +--- + +A new module registry implementation is available via the `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry). The new registry resolves module specifiers as URLs rather than filesystem-style paths, and includes several new capabilities and performance improvements. + +## New capabilities + +- **`import.meta`** -- `import.meta.url`, `import.meta.main`, and `import.meta.resolve()` are now supported. +- **URL-based specifier resolution** -- relative imports resolve the same way `new URL(specifier, base)` does. Query strings and fragments create distinct module instances, matching browser behavior. +- **Singleton `node:` built-ins** -- `node:` modules return the same instance whether accessed via static `import`, dynamic `import()`, `require()`, or `process.getBuiltinModule()`. +- **Import attribute validation** -- import attributes (`with { type: 'json' }`) are correctly validated. Unrecognized attribute keys and type mismatches throw errors instead of being silently ignored. +- **`require(esm)` support** -- `require()` on an ES module follows Node.js `require(esm)` rules, including the `'module.exports'` named export convention and the top-level `await` restriction. +- **WebAssembly source phase imports** -- import a compiled `WebAssembly.Module` directly with `import source mod from './module.wasm'` or `await import.source('./module.wasm')`. + +## Performance improvements + +- **Lazy compilation** -- modules are compiled on first import rather than all at startup, reducing cold start time for Workers with modules that are not always used. +- **Shared compiled code** -- compiled V8 bytecode is produced once and shared across all isolate replicas of a Worker, eliminating redundant compilation. + +For the full reference, refer to the [module registry documentation](/workers/reference/module-registry/). diff --git a/src/content/compatibility-flags/new-module-registry.md b/src/content/compatibility-flags/new-module-registry.md new file mode 100644 index 00000000000..2ac9245c183 --- /dev/null +++ b/src/content/compatibility-flags/new-module-registry.md @@ -0,0 +1,29 @@ +--- +_build: + publishResources: false + render: never + list: never + +name: "New module registry" +sort_date: "2026-08-05" +enable_flag: "new_module_registry" +disable_flag: "legacy_module_registry" +--- + +The `new_module_registry` flag replaces the Workers module registry with a new +implementation that resolves specifiers as URLs instead of filesystem-style paths. + +When enabled, the new registry provides: + +- `import.meta.url`, `import.meta.main`, and `import.meta.resolve()` support. +- Specifiers are parsed and resolved as URLs, including query strings and fragments. +- `node:` built-ins resolve to the same module instance regardless of how they are reached. +- Import attributes (`with { type: 'json' }`) are correctly validated. +- `require()` on an ES module follows Node.js `require(esm)` rules. +- Consistent error classes and messages across all loading paths. +- Lazy module compilation and shared compiled code across V8 isolate replicas. +- WebAssembly source phase imports. + +This flag does not have a default-on date yet. You must add it explicitly to +your `compatibility_flags`. For more information, refer to the +[module registry reference](/workers/reference/module-registry/). diff --git a/src/content/docs/workers/reference/module-registry.mdx b/src/content/docs/workers/reference/module-registry.mdx new file mode 100644 index 00000000000..0b5dc67c922 --- /dev/null +++ b/src/content/docs/workers/reference/module-registry.mdx @@ -0,0 +1,249 @@ +--- +title: Module registry +pcx_content_type: reference +description: Reference for the new Workers module registry, which provides import.meta support, URL-based specifier resolution, lazy compilation, and more. +products: + - workers +sidebar: + order: 5 +--- + +import { WranglerConfig } from "~/components"; + +The Workers runtime uses a module registry to resolve import specifiers, compile modules, and hand them to V8 for linking and execution. A new module registry implementation is available behind the `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/). + +The new registry resolves specifiers as URLs rather than filesystem-style paths. This enables `import.meta` support, more predictable resolution behavior, consistent errors, and performance improvements through lazy compilation and shared caching. + +:::note + +The `new_module_registry` flag does not have a default-on date yet. You must add it explicitly to opt in. Existing Workers continue to use the original registry. + +::: + +## Turn on the new module registry + +Add the `new_module_registry` compatibility flag to your Wrangler configuration file: + + + +```jsonc +{ + "compatibility_flags": ["new_module_registry"] +} +``` + + + +## `import.meta` + +The new registry adds support for `import.meta.url`, `import.meta.main`, and `import.meta.resolve()`. + +### `import.meta.url` + +Returns the URL of the current module as a string. + +```js +console.log(import.meta.url); +// "file:///bundle/index.js" +``` + +### `import.meta.main` + +Returns `true` only for the module configured as your Worker's entrypoint. Every other module returns `false`. + +```js +export default { + async fetch(request) { + return new Response(`main: ${import.meta.main}`); + // "main: true" for the entrypoint module + }, +}; +``` + +### `import.meta.resolve()` + +Resolves a specifier against the current module without importing it. This is a pure string transform that does not check whether the resolved URL corresponds to a real module. + +```js +import.meta.resolve("./utils.js"); // "file:///bundle/utils.js" +import.meta.resolve("./a/../utils.js"); // "file:///bundle/utils.js" +import.meta.resolve("fs"); // "node:fs" +``` + +`import.meta.resolve()` normalizes percent-encoding the same way `new URL()` does. It collapses dot segments but does not decode characters that are already percent-encoded. For example, `import.meta.resolve('%66oo.js')` resolves to `file:///bundle/%66oo.js`, not `file:///bundle/foo.js`. + +A `TypeError` is thrown for a specifier that cannot be parsed as a URL. + +## Specifiers are URLs + +Module specifiers are resolved as URLs using `new URL(specifier, base)`. This applies uniformly to all modules, though it matters most for modules that survive bundling: `node:` imports, Wasm/text/binary modules, and anything running without Wrangler's default bundler. + +Full URLs work as specifiers: + +```js +import { helper } from "file:///bundle/utils.js"; +``` + +### Query strings and fragments + +A specifier with a different query string or fragment is treated as a distinct module instance, following the same module-identity rules browsers use. Each instance gets its own `import.meta.url` and its own copy of top-level state. + +```js +// counter.js +let n = 0; +export function increment() { + return ++n; +} +``` + +```js +import { increment as incA } from "./counter.js?a"; +import { increment as incB } from "./counter.js?b"; + +incA(); // 1 +incA(); // 2 +incB(); // 1 — separate instance with its own copy of `n` +``` + +Importing the same specifier with the same query string returns the same instance. This does not provide a way to force re-evaluation on every import. + +## `node:` imports resolve consistently + +All paths to a `node:` built-in return the same module instance: static `import`, dynamic `import()`, `require()`, and `process.getBuiltinModule()`. + +```js +import { default as processA } from "node:process"; + +const viaImport = await import("node:process"); +const viaBuiltin = process.getBuiltinModule("node:process"); + +viaImport.default === processA; // true +viaBuiltin === processA; // true +``` + +A given built-in is a true singleton, including any module-level state, regardless of how your code reaches it. + +## Import attributes + +The new registry validates [import attributes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with) correctly. `json` is the only supported attribute type: + +```js +import data from "./config.json" with { type: "json" }; +``` + +### Unsupported attribute types + +The `text` and `bytes` attribute types are recognized but not yet supported. They track the [Import Text](https://github.com/tc39/proposal-import-text) and [Import Bytes](https://github.com/tc39/proposal-import-bytes) TC39 proposals, which have not yet reached Stage 4. Using them produces a specific error: + +```js +import msg from "./message.txt" with { type: "text" }; +// TypeError: Import attribute type "text" is not yet supported +``` + +### Unknown attribute keys + +Any attribute key other than `type` throws an error: + +```js +import data from "./config.json" with { type: "json", cache: "no" }; +// TypeError: Unsupported import attribute: "cache" +``` + +### Type mismatches + +If the specified type does not match the actual module type, an error is thrown: + +```js +import data from "./utils.js" with { type: "json" }; +// TypeError: Module "./utils.js" is not of type "json" +``` + +## `require(esm)` + +When `require()` is called on an ES module, the registry follows Node.js [`require(esm)`](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) behavior: + +- If the module has a string-named export called `'module.exports'`, that value is returned. This is Node.js' mechanism for letting an ES module control what `require()` returns. +- Otherwise, `require()` returns the module's namespace object. +- For workerd `node:` built-ins, `require()` returns the default export directly so that patterns like `require('node:buffer').Buffer` work as expected. + +```js +// utils.mjs +const impl = { hello: "world" }; +export { impl as "module.exports" }; +export default "not this"; +``` + +```js +import { createRequire } from "node:module"; + +const myRequire = createRequire(import.meta.url); +myRequire("./utils.mjs"); // { hello: "world" } +``` + +### Top-level `await` restriction + +If a module or any module in its import graph uses top-level `await`, `require()` throws an error. This matches Node.js' [`ERR_REQUIRE_ASYNC_MODULE`](https://nodejs.org/api/errors.html#err_require_async_module) restriction. Use `import()` for modules that rely on top-level `await`. + +```js +// async-init.mjs +await Promise.resolve(); +export const ready = true; +``` + +```js +myRequire("./async-init.mjs"); +// Error: Top-level await is not supported in this context +// for module: file:///bundle/async-init.mjs +``` + +### Bundler compatibility + +If a required module sets a truthy `__cjsUnwrapDefault` export (a marker used by some bundlers), the default export is returned. This ensures existing prebuilt bundles continue to work. + +## Error handling + +All module resolution errors use consistent classes and messages regardless of whether the failure occurs through a static `import`, dynamic `import()`, or `require()`: + +| Error | Class | Example message | +| --------------------------- | ----------- | ---------------------------------------------- | +| Module not found | `Error` | `Module not found: file:///bundle/nope.js` | +| Invalid specifier | `TypeError` | `Invalid module specifier: https://` | +| Circular dependency | `Error` | (V8 circular dependency error) | +| Unsupported import attribute | `TypeError` | `Unsupported import attribute: "cache"` | + +## Lazy compilation and shared caches + +The new registry includes two performance improvements: + +### Lazy compilation + +Modules are not compiled until they are first imported. In the original registry, all modules in a Worker's bundle are compiled at startup regardless of whether they are ever used. With the new registry, a WebAssembly module that is only used on a rarely-hit route does not add to cold start time for other routes. + +### Shared compiled code + +Cloudflare runs multiple V8 isolate replicas of a Worker to spread load across CPU cores. The original registry compiles the bundle independently in each replica. The new registry produces compiled V8 bytecode once and shares it across all replicas of that Worker. The first replica to import a given module pays the full compilation cost. Subsequent replicas use the cached result. This applies to both JavaScript and WebAssembly modules. + +## WebAssembly source phase imports + +You can import the compiled-but-not-instantiated form of a WebAssembly module using [source phase imports](https://github.com/nicolo-ribaudo/tc39-proposal-esm-integration/blob/main/proposals/source-phase-imports.md): + +```js +import source wasmModule from "./add.wasm"; + +export default { + async fetch() { + const instance = await WebAssembly.instantiate(wasmModule, {}); + return new Response(String(instance.exports.add(1, 2))); + }, +}; +``` + +Source phase imports can also be used dynamically: + +```js +const wasmModule = await import.source("./add.wasm"); +``` + +Both forms return a `WebAssembly.Module` directly. Source phase imports are only supported for WebAssembly modules. Using them on any other module type throws a `SyntaxError`. + +For more information on using WebAssembly in Workers, refer to [Wasm in JavaScript](/workers/runtime-apis/webassembly/javascript/). diff --git a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx index 352855c7310..53c9c31404c 100644 --- a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx +++ b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx @@ -86,6 +86,26 @@ export default { When invoked, this Worker should log `Hello from JavaScript: 42` and return `Success: 42`, demonstrating the ability to invoke Wasm methods with arguments from JavaScript and vice versa. +## Source phase imports + +With the [`new_module_registry`](/workers/configuration/compatibility-flags/#new-module-registry) compatibility flag, you can import a compiled-but-not-instantiated `WebAssembly.Module` directly using source phase imports: + +```js +import source wasmModule from "./simple.wasm"; + +const instance = await WebAssembly.instantiate(wasmModule, {}); +``` + +Source phase imports can also be used dynamically: + +```js +const wasmModule = await import.source("./simple.wasm"); +``` + +Both forms return a `WebAssembly.Module`. Source phase imports are only supported for WebAssembly modules. Using them on any other module type throws a `SyntaxError`. + +For more information, refer to [WebAssembly source phase imports](/workers/reference/module-registry/#webassembly-source-phase-imports). + ## Next steps In practice, you will likely compile a language of your choice (such as Rust) to WebAssembly binaries. Many languages provide a `bindgen` to simplify the interaction between JavaScript and Wasm. These tools may integrate with your JavaScript bundler, and provide an API other than the WebAssembly API for initializing and invoking your Wasm module. As an example, refer to the [Rust `wasm-bindgen` documentation](https://wasm-bindgen.github.io/wasm-bindgen/examples/without-a-bundler.html). From a65d3b1f24315400cacd7cbd0b63259f8d5b6bdd Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sat, 15 Aug 2026 16:58:58 -0700 Subject: [PATCH 2/4] [Workers] Expand new module registry documentation --- src/components/cf/CompatibilityFlags.astro | 4 +- .../2026-08-04-nodejs-compat-default.mdx | 5 +- .../2026-08-05-new-module-registry.mdx | 27 +- ...-modules-dont-export-a-module-namespace.md | 8 +- .../disable-top-level-await-in-require.md | 14 +- .../new-module-registry.md | 30 +- .../require-returns-default-export.md | 14 +- .../throw-on-unrecognized-import-assertion.md | 12 +- .../docs/dynamic-workers/api-reference.mdx | 16 +- .../examples/dynamic-workers-playground.mdx | 4 +- .../docs/dynamic-workers/getting-started.mdx | 12 +- .../multipart-upload-metadata.mdx | 10 + .../workers/languages/typescript/index.mdx | 6 +- .../local-development/wrangler-vs-vite.mdx | 8 + .../docs/workers/observability/errors.mdx | 6 + .../workers/observability/source-maps.mdx | 2 +- .../platform/infrastructure-as-code.mdx | 8 + src/content/docs/workers/platform/limits.mdx | 4 +- .../reference/migrate-to-module-workers.mdx | 2 + .../workers/reference/module-registry.mdx | 294 +++++++++--------- .../runtime-apis/bindings/worker-loader.mdx | 28 +- .../docs/workers/runtime-apis/nodejs/fs.mdx | 28 +- .../workers/runtime-apis/nodejs/index.mdx | 22 +- .../runtime-apis/webassembly/index.mdx | 36 ++- .../runtime-apis/webassembly/javascript.mdx | 106 ++++--- .../isolation-and-concurrency.mdx | 60 +--- .../vitest-integration/known-issues.mdx | 6 + .../tutorials/build-a-qr-code-generator.mdx | 6 +- .../docs/workers/vite-plugin/get-started.mdx | 10 + .../docs/workers/vite-plugin/index.mdx | 13 +- .../reference/migrating-from-wrangler-dev.mdx | 14 +- .../reference/vite-environments.mdx | 38 +++ .../docs/workers/wrangler/bundling.mdx | 43 ++- .../workers/wrangler/commands/workers.mdx | 6 +- .../docs/workers/wrangler/configuration.mdx | 16 +- .../docs/workers/wrangler/custom-builds.mdx | 10 +- src/content/partials/workers/source-maps.mdx | 4 +- 37 files changed, 565 insertions(+), 367 deletions(-) diff --git a/src/components/cf/CompatibilityFlags.astro b/src/components/cf/CompatibilityFlags.astro index 7703f1d9ea1..e0d0c62418e 100644 --- a/src/components/cf/CompatibilityFlags.astro +++ b/src/components/cf/CompatibilityFlags.astro @@ -17,9 +17,7 @@ const { experimental } = props.parse(Astro.props); let flags = await getCollection("compatibility-flags"); -if (experimental) { - flags = flags.filter((x) => x.data.experimental); -} +flags = flags.filter((x) => Boolean(x.data.experimental) === experimental); flags.sort( (a, b) => diff --git a/src/content/changelog/workers/2026-08-04-nodejs-compat-default.mdx b/src/content/changelog/workers/2026-08-04-nodejs-compat-default.mdx index 60786187e2a..9dfeacf19ee 100644 --- a/src/content/changelog/workers/2026-08-04-nodejs-compat-default.mdx +++ b/src/content/changelog/workers/2026-08-04-nodejs-compat-default.mdx @@ -16,8 +16,9 @@ dates because the compatibility date enables the same behavior. This means all [Node.js built-in APIs](/workers/runtime-apis/nodejs/) supported by the Workers runtime are available by default, including `node:crypto`, `node:buffer`, `node:stream`, `node:net`, `node:dns`, `node:fs`, `node:http`, -and more. npm packages that depend on these APIs will work without additional -configuration. +and more. npm packages can use these APIs without additional runtime +configuration. Package compatibility still depends on the build tool and any +other APIs that the package uses. Workers using an earlier compatibility date are not affected. They can still opt in by adding `nodejs_compat` to `compatibility_flags`. diff --git a/src/content/changelog/workers/2026-08-05-new-module-registry.mdx b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx index 750cdd21f1e..695dfc14db1 100644 --- a/src/content/changelog/workers/2026-08-05-new-module-registry.mdx +++ b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx @@ -1,25 +1,24 @@ --- -title: New module registry with import.meta, lazy compilation, and URL-based resolution -description: A new module registry implementation is available via the new_module_registry compatibility flag, bringing import.meta support, URL-based specifier resolution, lazy compilation, shared caching, and WebAssembly source phase imports. +title: Experimental module registry +description: Opt into URL-based module loading and runtime metadata. products: - workers date: 2026-08-05 --- -A new module registry implementation is available via the `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry). The new registry resolves module specifiers as URLs rather than filesystem-style paths, and includes several new capabilities and performance improvements. +The experimental `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry) selects a URL-based module registry. It provides new module APIs and more consistent loading behavior. -## New capabilities +Key changes include: -- **`import.meta`** -- `import.meta.url`, `import.meta.main`, and `import.meta.resolve()` are now supported. -- **URL-based specifier resolution** -- relative imports resolve the same way `new URL(specifier, base)` does. Query strings and fragments create distinct module instances, matching browser behavior. -- **Singleton `node:` built-ins** -- `node:` modules return the same instance whether accessed via static `import`, dynamic `import()`, `require()`, or `process.getBuiltinModule()`. -- **Import attribute validation** -- import attributes (`with { type: 'json' }`) are correctly validated. Unrecognized attribute keys and type mismatches throw errors instead of being silently ignored. -- **`require(esm)` support** -- `require()` on an ES module follows Node.js `require(esm)` rules, including the `'module.exports'` named export convention and the top-level `await` restriction. -- **WebAssembly source phase imports** -- import a compiled `WebAssembly.Module` directly with `import source mod from './module.wasm'` or `await import.source('./module.wasm')`. +- **Module metadata** — Use `import.meta.url`, `import.meta.main`, and `import.meta.resolve()`. +- **URL identity** — Queries and fragments identify separate bundle module instances. +- **Module loading** — Validate import attributes and follow Node.js `require(esm)` behavior. +- **Consistent errors** — Use the same error classes across loading paths. +- **Lazy compilation** — Compile modules when they are first imported. +- **Compiled artifacts** — Reuse cached compilation data when available. -## Performance improvements +Dynamic WebAssembly source phase imports through `import.source()` require the new registry. Static `import source` works with both registries. -- **Lazy compilation** -- modules are compiled on first import rather than all at startup, reducing cold start time for Workers with modules that are not always used. -- **Shared compiled code** -- compiled V8 bytecode is produced once and shared across all isolate replicas of a Worker, eliminating redundant compilation. +Use [Wrangler](/workers/wrangler/) 4.87.0 or later. For Vite projects, use [Cloudflare Vite plugin](/workers/vite-plugin/) 1.35.0 or later. -For the full reference, refer to the [module registry documentation](/workers/reference/module-registry/). +For configuration, tooling caveats, and API details, refer to the [module registry reference](/workers/reference/module-registry/). For WebAssembly examples, refer to [Wasm in JavaScript](/workers/runtime-apis/webassembly/javascript/#use-source-phase-imports). diff --git a/src/content/compatibility-flags/commonjs-modules-dont-export-a-module-namespace.md b/src/content/compatibility-flags/commonjs-modules-dont-export-a-module-namespace.md index 045bd34de4d..73711f70ae5 100644 --- a/src/content/compatibility-flags/commonjs-modules-dont-export-a-module-namespace.md +++ b/src/content/compatibility-flags/commonjs-modules-dont-export-a-module-namespace.md @@ -11,4 +11,10 @@ enable_flag: "export_commonjs_default" disable_flag: "export_commonjs_namespace" --- -CommonJS modules were previously exporting a module namespace (an object like `{ default: module.exports }`) rather than exporting only the `module.exports`. When this flag is enabled, the export is fixed. +The `export_commonjs_default` and `export_commonjs_namespace` flags apply only to the legacy module registry. + +The legacy registry previously exported a module namespace like `{ default: module.exports }`. The `export_commonjs_default` flag exports `module.exports` directly. + +The `export_commonjs_namespace` flag restores the previous namespace behavior. + +Neither flag affects the new module registry. For its Node.js `require(esm)` behavior, refer to the [module registry reference](/workers/reference/module-registry/#use-require-with-es-modules). diff --git a/src/content/compatibility-flags/disable-top-level-await-in-require.md b/src/content/compatibility-flags/disable-top-level-await-in-require.md index c4dc9f0184b..e73946bf3e5 100644 --- a/src/content/compatibility-flags/disable-top-level-await-in-require.md +++ b/src/content/compatibility-flags/disable-top-level-await-in-require.md @@ -11,14 +11,10 @@ enable_flag: "disable_top_level_await_in_require" disable_flag: "enable_top_level_await_in_require" --- -Workers implements the ability to use the Node.js style `require(...)` method -to import modules in the Worker bundle. Historically, this mechanism allowed -required modules to use top-level await. This, however, is not Node.js -compatible. +The `disable_top_level_await_in_require` and `enable_top_level_await_in_require` flags apply only to the legacy module registry. -The `disable_top_level_await_in_require` compat flag will cause `require()` -to fail if the module uses a top-level await. This flag is default enabled -with a compatibility date of 2024-12-02 or later. +The legacy registry originally allowed required modules to use top-level `await`. The `disable_top_level_await_in_require` flag makes `require()` fail when the required module uses top-level `await`. -To restore the original behavior allowing top-level await, use the -`enable_top_level_await_in_require` compatibility flag. +It is enabled by default for compatibility dates on or after `2024-12-02`. The `enable_top_level_await_in_require` flag restores the original behavior. + +The [new module registry](/workers/reference/module-registry/#use-require-with-es-modules) always rejects `require()` when the target module or its import graph uses top-level `await`. Neither legacy flag changes this behavior. diff --git a/src/content/compatibility-flags/new-module-registry.md b/src/content/compatibility-flags/new-module-registry.md index 2ac9245c183..bd6f656fbce 100644 --- a/src/content/compatibility-flags/new-module-registry.md +++ b/src/content/compatibility-flags/new-module-registry.md @@ -6,24 +6,26 @@ _build: name: "New module registry" sort_date: "2026-08-05" +experimental: true enable_flag: "new_module_registry" disable_flag: "legacy_module_registry" --- -The `new_module_registry` flag replaces the Workers module registry with a new -implementation that resolves specifiers as URLs instead of filesystem-style paths. +The experimental `new_module_registry` flag selects a URL-based Workers module registry. Use `legacy_module_registry` to select the legacy registry. When enabled, the new registry provides: -- `import.meta.url`, `import.meta.main`, and `import.meta.resolve()` support. -- Specifiers are parsed and resolved as URLs, including query strings and fragments. -- `node:` built-ins resolve to the same module instance regardless of how they are reached. -- Import attributes (`with { type: 'json' }`) are correctly validated. -- `require()` on an ES module follows Node.js `require(esm)` rules. -- Consistent error classes and messages across all loading paths. -- Lazy module compilation and shared compiled code across V8 isolate replicas. -- WebAssembly source phase imports. - -This flag does not have a default-on date yet. You must add it explicitly to -your `compatibility_flags`. For more information, refer to the -[module registry reference](/workers/reference/module-registry/). +- `import.meta` URL, path, entrypoint, and resolution metadata +- URL-based specifiers with query and fragment identities +- Import attribute validation and Node.js `require(esm)` behavior +- Consistent errors across module loading paths +- Lazy compilation and reusable compiled artifacts +- Dynamic WebAssembly source phase imports + +Static `import source` works with both registries. Dynamic `import.source()` requires the new registry. + +This flag has no default date. Add `new_module_registry` explicitly to opt in. + +This flag only applies to JavaScript Workers. Python Workers ignore it and use the legacy registry. + +For configuration and tooling requirements, refer to the [module registry reference](/workers/reference/module-registry/). diff --git a/src/content/compatibility-flags/require-returns-default-export.md b/src/content/compatibility-flags/require-returns-default-export.md index 78652de0dd9..85963c26f20 100644 --- a/src/content/compatibility-flags/require-returns-default-export.md +++ b/src/content/compatibility-flags/require-returns-default-export.md @@ -11,8 +11,16 @@ enable_flag: "require_returns_default_export" disable_flag: "require_returns_namespace" --- -When `require_returns_default_export` is enabled, `require()` will return the default export of a module if it exists. If the default export does not exist, it falls back to returning a mutable copy of the module namespace object. +The `require_returns_default_export` and `require_returns_namespace` flags apply only to the legacy module registry. -This matches the behavior that Node.js uses for `require(esm)`, where the default export is returned when available. This flag is useful for frameworks like Next.js that expect to be able to patch module exports. +With the legacy registry, `require_returns_default_export` makes `require()` return a module's default export when one exists. Otherwise, it returns a mutable copy of the module namespace object. -Previously, `require()` always returned the module namespace object (an object like `{default: module.exports}`). +The `require_returns_namespace` flag restores the previous namespace behavior. + +Neither flag affects the [new module registry](/workers/reference/module-registry/#use-require-with-es-modules). The new registry follows Node.js `require(esm)` behavior and supports an additional compatibility marker for older bundler output. + +It returns the module namespace object by default. A string-named `'module.exports'` export controls the returned value. + +A truthy `__cjsUnwrapDefault` export returns the default export and takes precedence over those rules. + +For [`workerd` `node:` built-ins](/workers/reference/module-registry/#load-nodejs-built-ins-consistently), `require()` returns the default export directly. diff --git a/src/content/compatibility-flags/throw-on-unrecognized-import-assertion.md b/src/content/compatibility-flags/throw-on-unrecognized-import-assertion.md index 15ea7891dc4..2530ca239bb 100644 --- a/src/content/compatibility-flags/throw-on-unrecognized-import-assertion.md +++ b/src/content/compatibility-flags/throw-on-unrecognized-import-assertion.md @@ -11,12 +11,10 @@ enable_flag: "throw_on_unrecognized_import_assertion" disable_flag: "ignore_unrecognized_import_assertion" --- -The `throw_on_unrecognized_import_assertion` flag controls how Workers handle -import attributes that are not recognized by the runtime. Previously, Workers -would ignore all import attributes, which is not compliant with the -specification. Runtimes are expected to throw an error when an import -attribute is encountered that is not recognized. +The `throw_on_unrecognized_import_assertion` and `ignore_unrecognized_import_assertion` flags apply only to the legacy module registry. -When the `ignore_unrecognized_import_assertion` flag is set, Workers will -ignore unrecognized import attributes. +The legacy registry previously ignored unrecognized import attributes. The `throw_on_unrecognized_import_assertion` flag rejects them instead. +The `ignore_unrecognized_import_assertion` flag restores the previous behavior. + +The [new module registry](/workers/reference/module-registry/#validate-import-attributes) always validates import attributes. Neither legacy flag changes this behavior. diff --git a/src/content/docs/dynamic-workers/api-reference.mdx b/src/content/docs/dynamic-workers/api-reference.mdx index 61e9708e412..ce913ad4940 100644 --- a/src/content/docs/dynamic-workers/api-reference.mdx +++ b/src/content/docs/dynamic-workers/api-reference.mdx @@ -40,6 +40,10 @@ You could, for example, use IDs of the form `:`, wh It is never guaranteed that two requests will go to the same isolate. Even if you use the same `WorkerStub` to make multiple requests, they could execute in different isolates. The callback passed to `loader.get()` could be called any number of times (although it is unusual for it to be called more than once). +Loader caching and module caching are separate. Each isolate has its own module evaluation cache and evaluates each module instance once. + +When local development enables the [new module registry](/workers/reference/module-registry/), compiled JavaScript and WebAssembly artifacts can be shared across isolate replicas. Module-level state remains local to each isolate. + ### `WorkerCode` This is the structure returned by `getCodeCallback` to represent a worker. @@ -56,13 +60,21 @@ An optional list of [compatibility flags](/workers/configuration/compatibility-f If true, then experimental compatibility flags will be permitted in `compatibilityFlags`. In order to set this, the worker calling the loader must itself have the compatibility flag `"experimental"` set. Experimental flags cannot be enabled in production. +The new module registry is experimental. Dynamic Workers therefore cannot use it in production. + #### mainModule -The name of the Worker's main module. This must be one of the modules listed in `modules`. +The runtime identifier for the Worker's main module. This must match one of the keys in `modules`. #### modules -A dictionary object mapping module names to their string contents. If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`. +A dictionary object mapping runtime module identifiers to their contents. These identifiers name uploaded runtime modules, not original source files. + +In local development with the [new module registry](/workers/reference/module-registry/), ordinary JavaScript module identifiers are canonicalized under `file:///bundle/`. Imports resolve against that registry, and URL specifiers do not fetch network resources. + +Use ordinary module names unless the upload API documents support for URL-shaped names. These rules do not describe Python import resolution. + +If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`. A module's content can also be specified as an object, in order to specify its type independent from the name. The allowed objects are: diff --git a/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx b/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx index 76ac0885075..04f83c88074 100644 --- a/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx +++ b/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx @@ -78,7 +78,9 @@ const response = await worker.getEntrypoint().fetch(request); -In the playground, you can see this in action — run the same Dynamic Worker twice and the second request shows a cached result with 0ms cold start, since the build and load phases are skipped entirely. +In the playground, a cache hit can skip the build and load phases. Reuse is not guaranteed, so a later request can still create a new isolate or call the code callback again. + +Loader reuse is separate from module caching. Each isolate retains its own module-level state. ## Observability with Tail Workers diff --git a/src/content/docs/dynamic-workers/getting-started.mdx b/src/content/docs/dynamic-workers/getting-started.mdx index 29bd3c49d9c..3ae0d8ad8ef 100644 --- a/src/content/docs/dynamic-workers/getting-started.mdx +++ b/src/content/docs/dynamic-workers/getting-started.mdx @@ -14,10 +14,10 @@ You can create a Worker that spins up other Workers, called Dynamic Workers, at Dynamic Workers support two loading modes: -- `load(code)` creates a fresh Dynamic Worker for one-time execution. -- `get(id, callback)` caches a Dynamic Worker by ID so it can stay warm across requests. +- `load(code)` creates a fresh Dynamic Worker without an ID cache. +- `get(id, callback)` lets the runtime reuse code or a warm isolate by ID. -`load()` is best for one-time code execution, for example when using [Code Mode](/agents/tools/codemode/). `get(id, callback)` is better when the same code will receive subsequent requests, for example when you are building applications. +Use `load()` for one-time code execution, such as [Code Mode](/agents/tools/codemode/). Use `get()` when subsequent requests can use identical code. Reuse is not guaranteed. ### Try it out @@ -99,11 +99,13 @@ In this example, `env.LOADER.load()` creates a Dynamic Worker from the code defi `worker.getEntrypoint().fetch(request)` sends the incoming request to the Dynamic Worker's `fetch()` handler, which processes it and returns a response. +`mainModule` and the `modules` keys are runtime module identifiers. They do not represent original source paths. For import resolution details, refer to the [module registry reference](/workers/reference/module-registry/). + ### Reusing a Dynamic Worker across requests -If you expect to load the exact same Worker more than once, use [`get(id, callback)`](/dynamic-workers/api-reference/#get) instead of `load()`. The `id` should be a unique string identifying the particular code you intend to load. When the runtime sees the same `id` again, it can reuse the existing Worker instead of creating a new one, if it hasn't been evicted yet. +If you expect to load identical code more than once, use [`get(id, callback)`](/dynamic-workers/api-reference/#get) instead of `load()`. The `id` should identify one exact code and configuration version. The runtime can reuse a warm Worker for that ID, but reuse is not guaranteed. -The callback you provide will only be called if the Worker is not already loaded. This lets you skip loading the code from storage when the Worker is already running. +The runtime calls your callback when it needs the Worker code. It can call the callback more than once, so return identical content for each ID. diff --git a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx index 70a56b0b83e..9a0367a4a35 100644 --- a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx +++ b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx @@ -31,6 +31,16 @@ If you're using the [Workers Script Upload API](/api/resources/workers/subresour } ``` +## Name modules + +Each multipart file part name is a runtime module identifier. The `main_module` value selects one of those identifiers as the entrypoint. Module identifiers name uploaded runtime modules or output chunks, not original source files. + +For JavaScript Workers using the [new module registry](/workers/reference/module-registry/), ordinary identifiers are canonicalized under `file:///bundle/`. Imports link against matching uploaded registry entries. + +URL specifiers resolve through the registry and do not fetch network resources. Use ordinary module names unless the upload API documents support for URL-shaped names. + +If your build emits multiple modules or chunks, upload every output as a separate multipart part. Preserve each generated name and content type so imports continue to resolve. + :::note See examples of metadata being used with the Workers Script Upload API [here](/workers/platform/infrastructure-as-code#cloudflare-rest-api). diff --git a/src/content/docs/workers/languages/typescript/index.mdx b/src/content/docs/workers/languages/typescript/index.mdx index e87492b545a..d5fdffe2534 100644 --- a/src/content/docs/workers/languages/typescript/index.mdx +++ b/src/content/docs/workers/languages/typescript/index.mdx @@ -53,7 +53,7 @@ See [the `wrangler types` command docs](/workers/wrangler/commands/general/#type This will generate a `d.ts` file and (by default) save it to `worker-configuration.d.ts`. This will include `Env` types based on your Worker bindings _and_ runtime types based on your Worker's compatibility date and flags. -You should then add that file to your `tsconfig.json`'s `compilerOptions.types` array. If you have the `nodejs_compat` compatibility flag, you should also install `@types/node`. +You should then add that file to your `tsconfig.json`'s `compilerOptions.types` array. If Node.js compatibility is enabled by your compatibility date or flags, you should also install `@types/node`. You can commit your types file to git if you wish. @@ -105,9 +105,9 @@ You can now remove any imports from `@cloudflare/workers-types` in your Worker c Note that if you have specified a custom path for the runtime types file, you should use that in your `compilerOptions.types` array instead of the default path. -#### 4. Add @types/node if you are using [`nodejs_compat`](/workers/runtime-apis/nodejs/) (Optional) +#### 4. Add @types/node if you use [Node.js compatibility](/workers/runtime-apis/nodejs/) (Optional) -If you are using the `nodejs_compat` compatibility flag, you should also install `@types/node`. +If Node.js compatibility is enabled by your compatibility date or flags, install `@types/node`. diff --git a/src/content/docs/workers/local-development/wrangler-vs-vite.mdx b/src/content/docs/workers/local-development/wrangler-vs-vite.mdx index 738a4d01f2b..a3c4e0f4f31 100644 --- a/src/content/docs/workers/local-development/wrangler-vs-vite.mdx +++ b/src/content/docs/workers/local-development/wrangler-vs-vite.mdx @@ -25,6 +25,8 @@ Choose based on the build tools your project uses. You can also use the Vite plu | Multi-Worker development | Supported | Supported | | Frontend and server-side rendering frameworks | Use the framework build output | Integrates with Vite-powered frameworks | | Build pipeline | Uses Wrangler's bundler or a custom build | Uses Vite transformations, Hot Module Replacement, and plugins | +| Default JavaScript output | One entry module | One entry chunk plus any generated chunks | +| Installed npm packages | Resolution handled by `esbuild` | Resolution handled by Vite | | Deployment and resource management | Supported | Use Wrangler after `vite build` | | [Rust Workers](/workers/languages/rust/) | Supported | Not supported | | [Python Workers](/workers/languages/python/) | Use [`pywrangler`](/workers/languages/python/) instead of `wrangler` | Not supported | @@ -33,6 +35,12 @@ Use the [Cloudflare Vite plugin](/workers/vite-plugin/) when your project alread Use [`wrangler dev`](/workers/wrangler/commands/general/#dev) when your project does not use Vite or you want a direct command-line workflow. Wrangler also provides deployment and resource management commands. +Both tools handle installed npm package resolution during a build. Resolution and runtime support vary by package. + +The Vite plugin supports Vite 6.1, 7, and 8. Vite 8 uses Rolldown, which can emit multiple output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. + +The Workers [module registry](/workers/reference/module-registry/) loads deployed runtime modules. Its features do not require Vite and apply to modules produced by other build tools. + For local development that requires deployed resources, both tools support [remote bindings](/workers/local-development/#remote-bindings). Your Worker runs locally while selected bindings connect to deployed Cloudflare resources. For configuration differences when moving an existing project, refer to [Migrating from wrangler dev](/workers/vite-plugin/reference/migrating-from-wrangler-dev/). diff --git a/src/content/docs/workers/observability/errors.mdx b/src/content/docs/workers/observability/errors.mdx index 9d928a6e679..49148ad62a7 100644 --- a/src/content/docs/workers/observability/errors.mdx +++ b/src/content/docs/workers/observability/errors.mdx @@ -222,6 +222,12 @@ This means that you are doing work in the top-level scope of your Worker that ta This means that you are doing work in the top-level scope of your Worker that allocates more than the [memory limit (128 MB)](/workers/platform/limits/#memory) of memory. +### Module loading errors + +With the [new module registry](/workers/reference/module-registry/), errors in the statically reached startup graph appear during upload validation. A separately uploaded lazy module compiles on first import. Its errors can instead appear during the invocation that first imports it. + +A missing module throws an `Error`. An invalid specifier or unsupported import attribute throws a `TypeError`. The registry uses consistent resolution errors across static `import`, dynamic `import()`, and `require()`. + ## Runtime errors Runtime errors will occur within the runtime, do not throw up an error page, and are not visible to the end user. Runtime errors are detected by the user with logs. diff --git a/src/content/docs/workers/observability/source-maps.mdx b/src/content/docs/workers/observability/source-maps.mdx index 60718c6644c..8eb6181316b 100644 --- a/src/content/docs/workers/observability/source-maps.mdx +++ b/src/content/docs/workers/observability/source-maps.mdx @@ -80,7 +80,7 @@ Let's see how source maps can simplify debugging an error in the ComplexCalculat ![Stack Trace without Source Map remapping](~/assets/images/workers-observability/without-source-map.png) -With **no source maps uploaded**: notice how all the Javascript has been minified to one file, so the stack trace is missing information on file name, shows incorrect line numbers, and incorrectly references `js` instead of `ts`. +With **no source maps uploaded**: the stack trace references generated JavaScript output. Depending on your bundler, this output can contain one module or several chunks. The trace lacks original file names and line numbers. ![Stack Trace with Source Map remapping](~/assets/images/workers-observability/with-source-map.png) diff --git a/src/content/docs/workers/platform/infrastructure-as-code.mdx b/src/content/docs/workers/platform/infrastructure-as-code.mdx index a6523ff9688..2a9efc384d4 100644 --- a/src/content/docs/workers/platform/infrastructure-as-code.mdx +++ b/src/content/docs/workers/platform/infrastructure-as-code.mdx @@ -30,6 +30,14 @@ wrangler deploy --dry-run --outdir build When using Wrangler for building and a different method for uploading, make sure to copy all of your config from `wrangler.json` into your Terraform config or API request. This is especially important with `compatibility_date` or flags your script relies on. +:::caution[Preserve all build outputs] + +Bundlers can emit an entrypoint and additional runtime modules or chunks. Upload every output with its generated name and content type. For multipart uploads, preserve each output as a separate part and set `main_module` to the generated entrypoint name. Uploading only the entrypoint can break imports. + +Module names are runtime identifiers. For details about URL canonicalization and linking, refer to the [module registry reference](/workers/reference/module-registry/). + +::: + ## Terraform In this example, you need a local file named `my-script.mjs` with script content similar to the below examples. Learn more about the [Cloudflare Terraform Provider](/terraform/), and refer to the [Workers script resource example](https://github.com/cloudflare/terraform-provider-cloudflare/blob/main/examples/resources/cloudflare_workers_script/resource.tf) for all available resource settings. diff --git a/src/content/docs/workers/platform/limits.mdx b/src/content/docs/workers/platform/limits.mdx index 6ac47be1255..e4d8674ae1d 100644 --- a/src/content/docs/workers/platform/limits.mdx +++ b/src/content/docs/workers/platform/limits.mdx @@ -288,7 +288,9 @@ To reduce Worker size: | ------------ | -------- | | Startup time | 1 second | -A Worker must parse and execute its global scope (top-level code outside of handlers) within 1 second. Larger bundles and expensive initialization code in global scope increase startup time. +A Worker must process its startup module graph and execute global scope within 1 second. Larger startup graphs and expensive top-level initialization increase startup time. + +With the [new module registry](/workers/reference/module-registry/), this startup graph includes modules reached through static imports. Separately uploaded modules first reached by a later dynamic import compile on that first import. They do not add to initial startup time. When the platform rejects a deployment because the Worker exceeds the startup time limit, the validation returns the error `Script startup exceeded CPU time limit` (error code `10021`). Wrangler automatically generates a CPU profile that you can import into Chrome DevTools or open in VS Code. Refer to [`wrangler check startup`](/workers/wrangler/commands/workers/#startup) for more details. diff --git a/src/content/docs/workers/reference/migrate-to-module-workers.mdx b/src/content/docs/workers/reference/migrate-to-module-workers.mdx index b331b443887..623b3aded81 100644 --- a/src/content/docs/workers/reference/migrate-to-module-workers.mdx +++ b/src/content/docs/workers/reference/migrate-to-module-workers.mdx @@ -12,6 +12,8 @@ import { WranglerConfig, TypeScriptExample } from "~/components"; This guide will show you how to migrate your Workers from the [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) format to the [ES modules](https://blog.cloudflare.com/workers-javascript-modules/) format. +For module identifiers, import resolution, and module caching, refer to the [module registry reference](/workers/reference/module-registry/). + ## Advantages of migrating There are several reasons to migrate your Workers to the ES modules format: diff --git a/src/content/docs/workers/reference/module-registry.mdx b/src/content/docs/workers/reference/module-registry.mdx index 0b5dc67c922..e21ef400afb 100644 --- a/src/content/docs/workers/reference/module-registry.mdx +++ b/src/content/docs/workers/reference/module-registry.mdx @@ -1,249 +1,255 @@ --- title: Module registry pcx_content_type: reference -description: Reference for the new Workers module registry, which provides import.meta support, URL-based specifier resolution, lazy compilation, and more. +description: Learn how Workers resolves runtime modules and provides module metadata. products: - workers sidebar: order: 5 --- -import { WranglerConfig } from "~/components"; +import { TypeScriptExample, WranglerConfig } from "~/components"; -The Workers runtime uses a module registry to resolve import specifiers, compile modules, and hand them to V8 for linking and execution. A new module registry implementation is available behind the `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/). +The Workers runtime uses a module registry to load modules. An experimental registry is available through a [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry). -The new registry resolves specifiers as URLs rather than filesystem-style paths. This enables `import.meta` support, more predictable resolution behavior, consistent errors, and performance improvements through lazy compilation and shared caching. +The registry resolves module specifiers as URLs. It also provides module metadata, lazy compilation, and consistent loading behavior. -:::note +## Configure the registry -The `new_module_registry` flag does not have a default-on date yet. You must add it explicitly to opt in. Existing Workers continue to use the original registry. - -::: - -## Turn on the new module registry - -Add the `new_module_registry` compatibility flag to your Wrangler configuration file: +To use the experimental registry, add its flag to your [Wrangler](/workers/wrangler/) configuration: -```jsonc -{ - "compatibility_flags": ["new_module_registry"] -} +```toml +name = "my-worker" +main = "src/index.ts" +compatibility_date = "$today" +compatibility_flags = ["new_module_registry"] ``` -## `import.meta` +The following flags select the registry implementation: -The new registry adds support for `import.meta.url`, `import.meta.main`, and `import.meta.resolve()`. +| Behavior | Compatibility flag | +| - | - | +| Use the new registry | `new_module_registry` | +| Use the legacy registry | `legacy_module_registry` | -### `import.meta.url` +Do not set both flags. The new registry has no default date, so you must opt in. -Returns the URL of the current module as a string. +### Use supported tools -```js -console.log(import.meta.url); -// "file:///bundle/index.js" -``` +- Use Wrangler 4.87.0 or later. +- Use [Cloudflare Vite plugin](/workers/vite-plugin/) 1.35.0 or later. +- Vitest Pool Workers does not support the new registry. -### `import.meta.main` +These versions support the registry protocol. For local support for `import.meta.filename` and `import.meta.dirname`, use Wrangler 4.122.0 or later, or Cloudflare Vite plugin 1.52.0 or later. -Returns `true` only for the module configured as your Worker's entrypoint. Every other module returns `false`. +Vite 8 does not currently bundle `import source` or `import.source()` syntax. Use Wrangler for the source phase import examples on this page. + +The new registry only applies to JavaScript Workers. Python Workers ignore `new_module_registry` and use the legacy registry. + +## Read module metadata + +The new registry adds these `import.meta` properties: + +| Property | Value | +| - | - | +| `import.meta.url` | Full URL for the current module | +| `import.meta.main` | `true` for the configured entrypoint | +| `import.meta.resolve()` | URL resolved against the current module | +| `import.meta.filename` | Absolute path for a `file:` module | +| `import.meta.dirname` | Parent path for a `file:` module | + +This example reads metadata from an entrypoint module: + +For TypeScript, install `@types/node` and include `node` in [`compilerOptions.types`](/workers/languages/typescript/#migrating). + + + +```ts +const metadata = { + url: import.meta.url, // "file:///bundle/index.js" + main: import.meta.main, // true + filename: import.meta.filename, // "/bundle/index.js" + dirname: import.meta.dirname, // "/bundle" + utilityUrl: import.meta.resolve("./utility.js"), +}; -```js export default { - async fetch(request) { - return new Response(`main: ${import.meta.main}`); - // "main: true" for the entrypoint module + fetch(): Response { + return Response.json(metadata); }, }; ``` -### `import.meta.resolve()` + -Resolves a specifier against the current module without importing it. This is a pure string transform that does not check whether the resolved URL corresponds to a real module. +`import.meta.filename` and `import.meta.dirname` only exist for `file:` URLs. They are absent from modules with other URL schemes. -```js -import.meta.resolve("./utils.js"); // "file:///bundle/utils.js" -import.meta.resolve("./a/../utils.js"); // "file:///bundle/utils.js" -import.meta.resolve("fs"); // "node:fs" -``` +`import.meta.resolve()` only transforms the specifier into a URL. It does not check whether the registry contains that URL. -`import.meta.resolve()` normalizes percent-encoding the same way `new URL()` does. It collapses dot segments but does not decode characters that are already percent-encoded. For example, `import.meta.resolve('%66oo.js')` resolves to `file:///bundle/%66oo.js`, not `file:///bundle/foo.js`. +Resolution follows `new URL(specifier, import.meta.url)`. It collapses dot segments but preserves existing percent-encoded bytes. -A `TypeError` is thrown for a specifier that cannot be parsed as a URL. +With [Node.js compatibility](/workers/runtime-apis/nodejs/), bare built-in names resolve to canonical `node:` URLs. For example, `import.meta.resolve("fs")` returns `node:fs`. -## Specifiers are URLs +Invalid URL specifiers cause `import.meta.resolve()` to throw a `TypeError`. -Module specifiers are resolved as URLs using `new URL(specifier, base)`. This applies uniformly to all modules, though it matters most for modules that survive bundling: `node:` imports, Wasm/text/binary modules, and anything running without Wrangler's default bundler. +## Resolve URL specifiers -Full URLs work as specifiers: +The registry resolves specifiers using web-standard URL rules. Relative specifiers resolve against the importing module URL. -```js -import { helper } from "file:///bundle/utils.js"; -``` +Build tools can rewrite imports before upload. Registry rules apply to the runtime modules that remain after bundling. -### Query strings and fragments +Full URLs can name runtime modules included in your Worker. Deployed Workers do not fetch source from remote URLs. -A specifier with a different query string or fragment is treated as a distinct module instance, following the same module-identity rules browsers use. Each instance gets its own `import.meta.url` and its own copy of top-level state. +For example, an `https:` specifier in a deployed Worker only loads a matching uploaded module. Otherwise, the import fails with a module-not-found error. -```js -// counter.js -let n = 0; -export function increment() { - return ++n; -} -``` +### Distinguish queries and fragments -```js -import { increment as incA } from "./counter.js?a"; -import { increment as incB } from "./counter.js?b"; +Queries and fragments form part of a bundle module identity. Different URLs create different module instances within an isolate. -incA(); // 1 -incA(); // 2 -incB(); // 1 — separate instance with its own copy of `n` -``` +This module stores state at the module top level: + + -Importing the same specifier with the same query string returns the same instance. This does not provide a way to force re-evaluation on every import. +```ts +let count = 0; -## `node:` imports resolve consistently +export function increment(): number { + return ++count; +} +``` -All paths to a `node:` built-in return the same module instance: static `import`, dynamic `import()`, `require()`, and `process.getBuiltinModule()`. + -```js -import { default as processA } from "node:process"; +Different queries create separate instances of that module: + + -const viaImport = await import("node:process"); -const viaBuiltin = process.getBuiltinModule("node:process"); +```ts +import { increment as incrementA } from "./counter.js?instance=a"; +import { increment as incrementB } from "./counter.js?instance=b"; -viaImport.default === processA; // true -viaBuiltin === processA; // true +incrementA(); // 1 +incrementA(); // 2 +incrementB(); // 1 ``` -A given built-in is a true singleton, including any module-level state, regardless of how your code reaches it. + -## Import attributes +Repeated imports of the same complete URL return one instance. They do not force the module to run again. -The new registry validates [import attributes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with) correctly. `json` is the only supported attribute type: +## Validate import attributes -```js -import data from "./config.json" with { type: "json" }; -``` +Static and dynamic imports accept [import attributes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with). The registry supports the `json` type. -### Unsupported attribute types +This example validates static and dynamic JSON imports: -The `text` and `bytes` attribute types are recognized but not yet supported. They track the [Import Text](https://github.com/tc39/proposal-import-text) and [Import Bytes](https://github.com/tc39/proposal-import-bytes) TC39 proposals, which have not yet reached Stage 4. Using them produces a specific error: + -```js -import msg from "./message.txt" with { type: "text" }; -// TypeError: Import attribute type "text" is not yet supported +```ts +import config from "./config.json" with { type: "json" }; + +const { default: dynamicConfig } = await import("./config.json", { + with: { type: "json" }, +}); + +export default { + fetch(): Response { + return Response.json({ config, dynamicConfig }); + }, +}; ``` -### Unknown attribute keys + -Any attribute key other than `type` throws an error: +The registry rejects invalid attributes with a `TypeError`: -```js -import data from "./config.json" with { type: "json", cache: "no" }; -// TypeError: Unsupported import attribute: "cache" -``` +- Unknown keys, such as `cache` +- Unsupported `text` and `bytes` types +- A type that does not match the module -### Type mismatches +## Load Node.js built-ins consistently -If the specified type does not match the actual module type, an error is thrown: +Access paths that resolve to the same `node:` built-in share one module instance per isolate. Static and dynamic imports expose its namespace. For runtime-provided `node:` built-ins, `require()` and `process.getBuiltinModule()` return the default export. -```js -import data from "./utils.js" with { type: "json" }; -// TypeError: Module "./utils.js" is not of type "json" -``` +`process.getBuiltinModule()` always selects the built-in. It does not select an uploaded module that shadows a bare built-in name. -## `require(esm)` +This behavior requires the applicable [Node.js compatibility flags](/workers/runtime-apis/nodejs/). -When `require()` is called on an ES module, the registry follows Node.js [`require(esm)`](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) behavior: +## Use `require()` with ES modules -- If the module has a string-named export called `'module.exports'`, that value is returned. This is Node.js' mechanism for letting an ES module control what `require()` returns. -- Otherwise, `require()` returns the module's namespace object. -- For workerd `node:` built-ins, `require()` returns the default export directly so that patterns like `require('node:buffer').Buffer` work as expected. +When `require()` loads an ES module, it follows Node.js [`require(esm)`](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) behavior. -```js -// utils.mjs -const impl = { hello: "world" }; -export { impl as "module.exports" }; -export default "not this"; -``` +By default, `require()` returns the module namespace object. A string-named `"module.exports"` export instead controls the returned value. -```js -import { createRequire } from "node:module"; +For compatibility with older bundler output, a truthy `__cjsUnwrapDefault` export returns the default export. This marker takes precedence over the other return rules and extends Node.js behavior. -const myRequire = createRequire(import.meta.url); -myRequire("./utils.mjs"); // { hello: "world" } -``` +The required module graph must be synchronous. If any module in that graph uses top-level `await`, `require()` throws an error. -### Top-level `await` restriction +This restriction still applies after another import evaluates the module. Use dynamic `import()` to load a graph containing top-level `await`. -If a module or any module in its import graph uses top-level `await`, `require()` throws an error. This matches Node.js' [`ERR_REQUIRE_ASYNC_MODULE`](https://nodejs.org/api/errors.html#err_require_async_module) restriction. Use `import()` for modules that rely on top-level `await`. +## Handle loading errors -```js -// async-init.mjs -await Promise.resolve(); -export const ready = true; -``` +Static imports, dynamic imports, and `require()` use consistent error classes: -```js -myRequire("./async-init.mjs"); -// Error: Top-level await is not supported in this context -// for module: file:///bundle/async-init.mjs -``` +| Failure | Error class | Message includes | +| - | - | - | +| Module not found | `Error` | Resolved module URL | +| Invalid specifier | `TypeError` | Invalid input specifier | +| Circular dependency | `Error` | Module instantiation failure | +| Unsupported import attribute | `TypeError` | Unsupported key or type | +| Import attribute mismatch | `TypeError` | Expected module type | +| Top-level `await` through `require()` | `Error` | Top-level `await` restriction | -### Bundler compatibility +## Understand compilation and evaluation -If a required module sets a truthy `__cjsUnwrapDefault` export (a marker used by some bundlers), the default export is returned. This ensures existing prebuilt bundles continue to work. +### Compile modules lazily -## Error handling +The new registry compiles modules when they are first imported. Modules that are never imported do not add compilation work at startup. -All module resolution errors use consistent classes and messages regardless of whether the failure occurs through a static `import`, dynamic `import()`, or `require()`: +Static dependencies of the entrypoint are still resolved during startup. Dynamic imports defer that work until the import runs. -| Error | Class | Example message | -| --------------------------- | ----------- | ---------------------------------------------- | -| Module not found | `Error` | `Module not found: file:///bundle/nope.js` | -| Invalid specifier | `TypeError` | `Invalid module specifier: https://` | -| Circular dependency | `Error` | (V8 circular dependency error) | -| Unsupported import attribute | `TypeError` | `Unsupported import attribute: "cache"` | +### Cache module evaluation -## Lazy compilation and shared caches +V8 evaluates each module instance once per isolate. Repeated imports return its existing namespace or cached evaluation error. -The new registry includes two performance improvements: +Queries and fragments can create separate instances. Each of those instances evaluates separately. -### Lazy compilation +### Reuse compiled artifacts -Modules are not compiled until they are first imported. In the original registry, all modules in a Worker's bundle are compiled at startup regardless of whether they are ever used. With the new registry, a WebAssembly module that is only used on a rarely-hit route does not add to cold start time for other routes. +Isolate replicas that share a registry can reuse V8 compilation data. WebAssembly modules can also reuse shared compiled artifacts. -### Shared compiled code +Each isolate still creates and evaluates its own module instances. Cache reuse is an optimization and does not guarantee one compilation across all replicas. -Cloudflare runs multiple V8 isolate replicas of a Worker to spread load across CPU cores. The original registry compiles the bundle independently in each replica. The new registry produces compiled V8 bytecode once and shares it across all replicas of that Worker. The first replica to import a given module pays the full compilation cost. Subsequent replicas use the cached result. This applies to both JavaScript and WebAssembly modules. +## Import WebAssembly source -## WebAssembly source phase imports +A static source phase import returns a compiled `WebAssembly.Module`. Static `import source` works with both module registries. -You can import the compiled-but-not-instantiated form of a WebAssembly module using [source phase imports](https://github.com/nicolo-ribaudo/tc39-proposal-esm-integration/blob/main/proposals/source-phase-imports.md): +This example instantiates a WebAssembly module without imports: ```js import source wasmModule from "./add.wasm"; +const instance = await WebAssembly.instantiate(wasmModule, {}); + export default { - async fetch() { - const instance = await WebAssembly.instantiate(wasmModule, {}); + fetch() { return new Response(String(instance.exports.add(1, 2))); }, }; ``` -Source phase imports can also be used dynamically: +Dynamic source phase imports require the new registry: ```js const wasmModule = await import.source("./add.wasm"); ``` -Both forms return a `WebAssembly.Module` directly. Source phase imports are only supported for WebAssembly modules. Using them on any other module type throws a `SyntaxError`. +Both forms only support WebAssembly modules. Other module types cause a `SyntaxError`. + +Vite 8 does not currently bundle this syntax. Use Wrangler 4.87.0 or later for these examples. -For more information on using WebAssembly in Workers, refer to [Wasm in JavaScript](/workers/runtime-apis/webassembly/javascript/). +For more information, refer to [Wasm in JavaScript](/workers/runtime-apis/webassembly/javascript/). diff --git a/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx b/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx index 6d30e88bf3f..cdcded5a20c 100644 --- a/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx +++ b/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx @@ -9,9 +9,9 @@ products: import { Type, MetaInfo, WranglerConfig } from "~/components"; -:::note[Dynamic Worker Loading is in closed beta] +:::note -The Worker Loader API is available in local development with Wrangler and workerd. But to run dynamic Workers on Cloudflare, you must [sign up for the closed beta](https://forms.gle/MoeDxE9wNiqdf8ri9). +For current setup and API guidance, refer to [Dynamic Workers](/dynamic-workers/). ::: @@ -36,7 +36,7 @@ Code Mode makes [AI SDK tools](/agents/tools/codemode/ai-sdk/), tools from an [M ## Basic usage -A Worker Loader is a binding with just one method, `get()`, which loads an isolate. Example usage: +A Worker Loader binding provides two loading methods. `load()` creates a fresh Worker without an ID cache. `get()` lets the runtime reuse code or a warm isolate by ID. The following example uses `get()`: ```js let id = "foo"; @@ -105,6 +105,12 @@ To add a dynamic worker loader binding to your worker, add it to your Wrangler c ## API Reference +### `load` + +load(code ): + +Loads a fresh Worker from the provided `WorkerCode`. Unlike `get()`, `load()` does not cache by ID. + ### `get` get(id , getCodeCallback ): @@ -123,6 +129,10 @@ You could, for example, use IDs of the form `:`, wh It is never guaranteed that two requests will go to the same isolate. Even if you use the same `WorkerStub` to make multiple requests, they could execute in different isolates. The callback passed to `loader.get()` could be called any number of times (although it is unusual for it to be called more than once). +Loader caching and module caching are separate. Each isolate has its own module evaluation cache and evaluates each module instance once. + +When local development enables the [new module registry](/workers/reference/module-registry/), compiled JavaScript and WebAssembly artifacts can be reused across isolate replicas. Module-level state remains local to each isolate. + ### `WorkerCode` This is the structure returned by `getCodeCallback` to represent a worker. @@ -139,13 +149,21 @@ An optional list of [compatibility flags](/workers/configuration/compatibility-f If true, then experimental compatibility flags will be permitted in `compatibilityFlags`. In order to set this, the worker calling the loader must itself have the compatibility flag `"experimental"` set. Experimental flags cannot be enabled in production. +The new module registry is experimental. Dynamic Workers therefore cannot use it in production. + #### mainModule -The name of the Worker's main module. This must be one of the modules listed in `modules`. +The runtime identifier for the Worker's main module. This must match one of the keys in `modules`. #### modules -A dictionary object mapping module names to their string contents. If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`. +A dictionary object mapping runtime module identifiers to their contents. These identifiers name uploaded runtime modules, not original source files. + +In local development with the [new module registry](/workers/reference/module-registry/), ordinary JavaScript identifiers are canonicalized under `file:///bundle/`. URL specifiers resolve through the registry and do not fetch network resources. + +Use ordinary module names unless the upload API documents support for URL-shaped names. These rules do not describe Python import resolution. + +If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`. A module's content can also be specified as an object, in order to specify its type independent from the name. The allowed objects are: diff --git a/src/content/docs/workers/runtime-apis/nodejs/fs.mdx b/src/content/docs/workers/runtime-apis/nodejs/fs.mdx index 5a58e204266..2acb8291018 100644 --- a/src/content/docs/workers/runtime-apis/nodejs/fs.mdx +++ b/src/content/docs/workers/runtime-apis/nodejs/fs.mdx @@ -13,13 +13,9 @@ import { Render } from "~/components"; You can use [`node:fs`](https://nodejs.org/api/fs.html) to access a virtual file system in Workers. -The `node:fs` module is available in Workers runtimes that support Node.js -compatibility using the `nodejs_compat` compatibility flag. Any Worker -running with `nodejs_compat` enabled and with a compatibility date of -`2025-09-01` or later will have access to `node:fs` by default. It is -also possible to enable `node:fs` on Workers with an earlier compatibility -date using a combination of the `nodejs_compat` and `enable_nodejs_fs_module` -flags. To disable `node:fs` you can set the `disable_nodejs_fs_module` flag. +The `node:fs` module requires Node.js compatibility and a compatibility date of `2025-09-01` or later. For compatibility dates on or after `2026-08-04`, Node.js compatibility is enabled by default. + +For compatibility dates from `2025-09-01` through `2026-08-03`, add the `nodejs_compat` flag. For earlier dates, add both `nodejs_compat` and `enable_nodejs_fs_module`. To turn off `node:fs`, add `disable_nodejs_fs_module`. ```js import { readFileSync, writeFileSync } from "node:fs"; @@ -30,8 +26,8 @@ writeFileSync("/tmp/abc.txt", "Hello, world!"); ``` The Workers Virtual File System (VFS) is a memory-based file system that allows -you to read modules included in your Worker bundle as read-only files, access a -directory for writing temporary files, or access common +you to read uploaded runtime modules as read-only files, access a directory for +writing temporary files, or access common [character devices](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html) like `/dev/null`, `/dev/random`, `/dev/full`, and `/dev/zero`. @@ -40,7 +36,7 @@ The directory structure initially looks like: ``` /bundle -└── (one file for each module in your Worker bundle) +└── (one file for each uploaded runtime module or output chunk) /tmp └── (empty, but you can write files, create directories, symlinks, etc) /dev @@ -51,10 +47,14 @@ The directory structure initially looks like: ``` -The `/bundle` directory contains the files for all modules included in your -Worker bundle, which you can read using APIs like `readFileSync` or -`read(...)`, etc. These are always read-only. Reading from the bundle -can be useful when you need to read a config file or a template. +The `/bundle` directory contains modules uploaded for runtime execution. These +can include generated output chunks and separately uploaded modules. They are +not necessarily your original source files. + +Uploaded modules are always read-only. You can read them with APIs such as +`readFileSync()` or `read()`. With the [new module registry](/workers/reference/module-registry/), +an ordinary module name such as `config.txt` has the URL +`file:///bundle/config.txt`. ```js import { readFileSync } from "node:fs"; diff --git a/src/content/docs/workers/runtime-apis/nodejs/index.mdx b/src/content/docs/workers/runtime-apis/nodejs/index.mdx index 1da0bc948b1..2a33e4e05b3 100644 --- a/src/content/docs/workers/runtime-apis/nodejs/index.mdx +++ b/src/content/docs/workers/runtime-apis/nodejs/index.mdx @@ -9,14 +9,24 @@ products: import { Render, WranglerConfig } from "~/components"; -When you write a Worker, you may need to import packages from [npm](https://www.npmjs.com/). Many npm packages rely on APIs from the [Node.js runtime](https://nodejs.org/en/about), and will not work unless these Node.js APIs are available. +Using a package from [npm](https://www.npmjs.com/) involves three separate layers: + +1. Your build tool resolves the installed package and processes its source modules. +2. The Workers [module registry](/workers/reference/module-registry/) loads the runtime modules included in your deployment. +3. The Workers runtime provides the APIs that the package calls. + +Wrangler uses [`esbuild`](/workers/wrangler/bundling/) for the first layer. The [Cloudflare Vite plugin](/workers/vite-plugin/) uses Vite instead. + +A package works only when all three layers are compatible. The runtime does not install packages or resolve package names from npm. + +Many npm packages rely on [Node.js runtime](https://nodejs.org/en/about) APIs. These packages require the corresponding APIs or compatible polyfills. Cloudflare Workers provides a subset of Node.js APIs in two forms: 1. As built-in APIs provided by the Workers Runtime. Most of these APIs are full implementations of the corresponding Node.js APIs, while a few are partially supported. -2. As polyfill shim implementations that [Wrangler](/workers/wrangler/) adds to your Worker's code, allowing it to import the module, but calling API methods will throw errors. +2. As polyfill shim implementations that Wrangler or the [Cloudflare Vite plugin](/workers/vite-plugin/) adds to your Worker's code. These shims allow imports, but some API methods throw errors. ## Get Started @@ -89,21 +99,21 @@ If an API you wish to use is missing and you want to suggest that Workers suppor Some Node.js modules are available as non-functional stubs. A stub can be imported or required, but does not provide a working implementation of the underlying Node.js API. These stubs exist so packages that check for the presence of a module can load in Workers, but they are not suitable for direct use in application code. -The following stubs are enabled automatically only when the `nodejs_compat` compatibility flag is enabled and your Worker's compatibility date is on or after the date shown. To enable one earlier, add the corresponding enable flag. To keep one unavailable after that date, add the corresponding disable flag. +The following stubs are enabled automatically when Node.js compatibility is active and your Worker's compatibility date is on or after the date shown. To enable one earlier, add the corresponding enable flag. To keep one unavailable after that date, add the corresponding disable flag. ### Node.js API Polyfills -Node.js APIs that are not yet supported in the Workers runtime are polyfilled via [Wrangler](/workers/wrangler/), which uses [unenv](https://github.com/unjs/unenv). If the `nodejs_compat` [compatibility flag](/workers/configuration/compatibility-flags/) is enabled, and your Worker's [compatibility date](/workers/configuration/compatibility-dates/) is 2024-09-23 or later, Wrangler will automatically inject polyfills into your Worker's code. +Node.js APIs that are not yet supported in the Workers runtime are polyfilled with [unenv](https://github.com/unjs/unenv). Wrangler and the Cloudflare Vite plugin can inject these polyfills during the build. This requires the `nodejs_compat` behavior described in [Get started](#get-started). -Adding polyfills maximizes compatibility with existing npm packages by providing modules with mocked methods. Calling these mocked methods will either noop or will throw an error with a message like: +Adding polyfills can improve compatibility with existing npm packages. Some polyfilled modules contain mocked methods that do nothing or throw an error like: ``` [unenv] is not implemented yet! ``` -This allows you to import packages that use these Node.js modules, even if certain methods are not supported. +This can let a package load even when some methods remain unsupported. Node.js compatibility does not guarantee compatibility with every npm package. ## Enable only AsyncLocalStorage diff --git a/src/content/docs/workers/runtime-apis/webassembly/index.mdx b/src/content/docs/workers/runtime-apis/webassembly/index.mdx index d4a1cb8a2d5..6a9c9a6a6ef 100644 --- a/src/content/docs/workers/runtime-apis/webassembly/index.mdx +++ b/src/content/docs/workers/runtime-apis/webassembly/index.mdx @@ -2,25 +2,25 @@ pcx_content_type: concept title: WebAssembly (Wasm) head: [] -description: Execute code written in a language other than JavaScript or write - an entire Cloudflare Worker in Rust. +description: Run code compiled from languages such as Rust with WebAssembly. products: - workers --- -import { DirectoryListing } from "~/components" +import { DirectoryListing } from "~/components"; -[WebAssembly](https://webassembly.org/) (abbreviated Wasm) allows you to compile languages like [Rust](/workers/languages/rust/), Go, or C to a binary format that can run in a wide variety of environments, including [web browsers](https://developer.mozilla.org/en-US/docs/WebAssembly#browser_compatibility), Cloudflare Workers, and other WebAssembly runtimes. +[WebAssembly](https://webassembly.org/) (Wasm) compiles languages such as [Rust](/workers/languages/rust/), Go, and C into binary modules. These modules run in [web browsers](https://developer.mozilla.org/en-US/docs/WebAssembly#browser_compatibility), Cloudflare Workers, and other Wasm runtimes. -You can use WebAssembly to: +You can use WebAssembly in these ways: -* Execute code written in a language other than JavaScript, via `WebAssembly.instantiate()`. - :::note - `WebAssembly.instantiate()` only supports pre-compiled modules as documented in the [web-standards documentation](/workers/runtime-apis/web-standards/#javascript-standards). - ::: +- Execute code from another language with `WebAssembly.instantiate()`. +- Import a compiled module with [source phase imports](/workers/runtime-apis/webassembly/javascript/#use-source-phase-imports). +- Write an entire Worker in Rust with Workers API bindings. -* Write an entire Cloudflare Worker in Rust, using bindings that make Workers' JavaScript APIs available directly from your Rust code. +:::note +`WebAssembly.instantiate()` only supports precompiled modules. Refer to [JavaScript standards](/workers/runtime-apis/web-standards/#javascript-standards). +::: Most programming languages can be compiled to Wasm, although support varies across languages and compilers. Guides are available for the following languages: @@ -28,19 +28,21 @@ Most programming languages can be compiled to Wasm, although support varies acro ## Supported proposals -WebAssembly is a rapidly evolving set of standards, with [many proposed APIs](https://webassembly.org/roadmap/) which are in various stages of development. In general, Workers supports the same set of features that are available in Google Chrome. +WebAssembly standards include [many proposed APIs](https://webassembly.org/roadmap/). Workers generally supports the features available in Google Chrome. ### SIMD SIMD is supported on Workers. For more information on using SIMD in WebAssembly, refer to [Fast, parallel applications with WebAssembly SIMD](https://v8.dev/features/simd). -### Threading +### Threads -Threading is not possible in Workers. Each Worker runs in a single thread, and the [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) API is not supported. +Threads are not available in Workers. Each Worker runs in one thread, and the [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) API is not supported. ## Binary size -Compiling to WebAssembly often requires including additional runtime dependencies. As a result, Workers that use WebAssembly are typically larger than an equivalent Worker written in JavaScript. The larger your Worker is, the longer it may take your Worker to start. Refer to [Worker startup time](https://developers.cloudflare.com/workers/platform/limits/#worker-startup-time) for more information. We recommend using tools like [`wasm-opt`](https://github.com/brson/wasm-opt-rs) to optimize the size of your Wasm binary. +Compiling to WebAssembly often adds runtime dependencies. Wasm Workers can therefore exceed equivalent JavaScript Worker sizes. + +Larger Workers can take longer to start. Refer to [Worker startup time](/workers/platform/limits/#worker-startup-time) and optimize binaries with tools such as [`wasm-opt`](https://github.com/brson/wasm-opt-rs). ## WebAssembly System Interface (WASI) @@ -48,7 +50,7 @@ The [WebAssembly System Interface](https://wasi.dev/) (abbreviated WASI) is a mo WASI is an earlier and more rapidly evolving set of standards than Wasm. WASI support is experimental on Cloudflare Workers, with only some syscalls implemented. Refer to our [open source implementation of WASI](https://github.com/cloudflare/workers-wasi), and [blog post about WASI on Workers](https://blog.cloudflare.com/announcing-wasi-on-workers/) demonstrating its use. -### Resources on WebAssembly +### WebAssembly resources -* [Serverless Rust with Cloudflare Workers](https://blog.cloudflare.com/cloudflare-workers-as-a-serverless-rust-platform/) -* [WebAssembly on Cloudflare Workers](https://blog.cloudflare.com/webassembly-on-cloudflare-workers/) +- [Serverless Rust with Cloudflare Workers](https://blog.cloudflare.com/cloudflare-workers-as-a-serverless-rust-platform/) +- [WebAssembly on Cloudflare Workers](https://blog.cloudflare.com/webassembly-on-cloudflare-workers/) diff --git a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx index 53c9c31404c..9f1aedfb787 100644 --- a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx +++ b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx @@ -1,6 +1,6 @@ --- title: Wasm in JavaScript -description: Import and instantiate WebAssembly modules in Cloudflare Workers using JavaScript. +description: Import and instantiate compiled binaries in Workers. pcx_content_type: how-to sidebar: order: 1 @@ -11,26 +11,25 @@ products: - workers --- -Wasm can be used from within a Worker written in JavaScript or TypeScript by importing a Wasm module, -and instantiating an instance of this module using [`WebAssembly.instantiate()`](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiate). This can be used to accelerate computationally intensive operations which do not involve significant I/O. +import { TypeScriptExample } from "~/components"; -This guide demonstrates the basics of Wasm and JavaScript interoperability. +You can import a WebAssembly (Wasm) module into a Worker. Then instantiate it with [`WebAssembly.instantiate()`](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiate). -## Simple Wasm Module +Wasm can accelerate compute-heavy operations with little input and output. -In this guide, you will use the WebAssembly Text Format to create a simple Wasm module to understand how imports and exports work. In practice, you would not write code in this format. You would instead use the programming language of your choice and compile directly to WebAssembly Binary Format (`.wasm`). +## Create a simple Wasm module -Review the following example module (`;;` denotes a comment): +This guide uses WebAssembly Text Format (WAT) to show imports and exports. For production, compile your preferred language directly into a `.wasm` binary. -```txt -;; src/simple.wat +The following WAT module imports and calls a JavaScript function: + +```txt title="src/simple.wat" (module - ;; Import a function from JavaScript named `imported_func` - ;; which takes a single i32 argument and assign to - ;; variable $i + ;; Import a function from JavaScript named `imported_func`. + ;; The function takes one i32 argument and is assigned to $i. (func $i (import "imports" "imported_func") (param i32)) - ;; Export a function named `exported_func` which takes a - ;; single i32 argument and returns an i32 + ;; Export a function named `exported_func`. + ;; The function takes one i32 argument and returns an i32. (func (export "exported_func") (param $input i32) (result i32) ;; Invoke `imported_func` with $input as argument local.get $input @@ -42,72 +41,93 @@ Review the following example module (`;;` denotes a comment): ) ``` -Using [`wat2wasm`](https://github.com/WebAssembly/wabt), convert the WAT format to WebAssembly Binary Format: +To create the binary, convert the WAT file with [`wat2wasm`](https://github.com/WebAssembly/wabt): ```sh wat2wasm src/simple.wat -o src/simple.wasm ``` -## Bundling +## Bundle the module + +[Wrangler](/workers/wrangler/) bundles modules ending in `.wasm` or `.wasm?module`. You can change this default with [module rules](/workers/wrangler/configuration/#bundling). + +For more information, refer to [Wrangler bundling](/workers/wrangler/bundling/). + +## Use Wasm from JavaScript -Wrangler will bundle any Wasm module that ends in `.wasm` or `.wasm?module`, so that it is available at runtime within your Worker. This is done using a default bundling rule which can be customized in the [Wrangler configuration file](/workers/wrangler/configuration/). Refer to [Wrangler Bundling](/workers/wrangler/bundling/) for more information. +Import the compiled module into your Worker. The default import provides a compiled `WebAssembly.Module`. -## Use from JavaScript +This example instantiates the module once in global scope. The handler then calls its exported function. -After you have converted the WAT format to WebAssembly Binary Format, import and use the Wasm module in your existing JavaScript or TypeScript Worker: + -```typescript -import mod from "./simple.wasm"; +```ts +import module from "./simple.wasm"; -// Define imports available to Wasm instance. const importObject = { imports: { - imported_func: (arg: number) => { - console.log(`Hello from JavaScript: ${arg}`); + imported_func: (argument: number) => { + console.log(`Hello from JavaScript: ${argument}`); }, }, }; -// Create instance of WebAssembly Module `mod`, supplying -// the expected imports in `importObject`. This should be -// done at the top level of the script to avoid instantiation on every request. -const instance = await WebAssembly.instantiate(mod, importObject); +const instance = await WebAssembly.instantiate(module, importObject); +const exportedFunction = instance.exports.exported_func as ( + argument: number, +) => number; export default { - async fetch() { - // Invoke the `exported_func` from our Wasm Instance with - // an argument. - const retval = instance.exports.exported_func(42); - // Return the return value! - return new Response(`Success: ${retval}`); + fetch(): Response { + const result = exportedFunction(42); + return new Response(`Success: ${result}`); }, }; ``` -When invoked, this Worker should log `Hello from JavaScript: 42` and return `Success: 42`, demonstrating the ability to invoke Wasm methods with arguments from JavaScript and vice versa. + -## Source phase imports +The Worker logs `Hello from JavaScript: 42`. It returns `Success: 42`. -With the [`new_module_registry`](/workers/configuration/compatibility-flags/#new-module-registry) compatibility flag, you can import a compiled-but-not-instantiated `WebAssembly.Module` directly using source phase imports: +## Use source phase imports + +Source phase imports use standard syntax to return a compiled `WebAssembly.Module`. They do not instantiate the module. + +Static `import source` works with the legacy and new registries. This example statically imports and instantiates the module: ```js import source wasmModule from "./simple.wasm"; -const instance = await WebAssembly.instantiate(wasmModule, {}); +const instance = await WebAssembly.instantiate(wasmModule, { + imports: { + imported_func: (argument) => { + console.log(`Hello from JavaScript: ${argument}`); + }, + }, +}); +const exportedFunction = instance.exports.exported_func; + +export default { + fetch() { + return new Response(String(exportedFunction(42))); + }, +}; ``` -Source phase imports can also be used dynamically: +Dynamic source phase imports require the [`new_module_registry`](/workers/configuration/compatibility-flags/#new-module-registry) compatibility flag: ```js const wasmModule = await import.source("./simple.wasm"); ``` -Both forms return a `WebAssembly.Module`. Source phase imports are only supported for WebAssembly modules. Using them on any other module type throws a `SyntaxError`. +Both forms only support WebAssembly modules. Other module types cause a `SyntaxError`. + +Vite 8 does not currently bundle source phase import syntax. Use Wrangler 4.87.0 or later for these examples. -For more information, refer to [WebAssembly source phase imports](/workers/reference/module-registry/#webassembly-source-phase-imports). +For registry configuration and tooling caveats, refer to [Module registry](/workers/reference/module-registry/). ## Next steps -In practice, you will likely compile a language of your choice (such as Rust) to WebAssembly binaries. Many languages provide a `bindgen` to simplify the interaction between JavaScript and Wasm. These tools may integrate with your JavaScript bundler, and provide an API other than the WebAssembly API for initializing and invoking your Wasm module. As an example, refer to the [Rust `wasm-bindgen` documentation](https://wasm-bindgen.github.io/wasm-bindgen/examples/without-a-bundler.html). +Compile your preferred language into a WebAssembly binary. Many languages provide binding generators for JavaScript interoperability. -Alternatively, to write your entire Worker in Rust, Workers provides many of the same [Runtime APIs](/workers/runtime-apis) and [bindings](/workers/runtime-apis/bindings/) when using the `workers-rs` crate. For more information, refer to the [Workers Rust guide](/workers/languages/rust/). +For Rust, refer to the [`wasm-bindgen` documentation](https://wasm-bindgen.github.io/wasm-bindgen/examples/without-a-bundler.html). To write an entire Worker in Rust, refer to the [Workers Rust guide](/workers/languages/rust/). diff --git a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx index 3f681a7deeb..a4694324ff1 100644 --- a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx +++ b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx @@ -10,8 +10,6 @@ products: - workers --- -import { WranglerConfig } from "~/components"; - Review how the Workers Vitest integration runs your tests, how it isolates tests from each other, and how it imports modules. ## Run tests @@ -33,62 +31,22 @@ By default, test files run concurrently. To make test files share the same stora ## Modules -Each Worker has its own module cache. As Workers are reused between test runs, their module caches are also reused. Vitest invalidates parts of the module cache at the start of each test run based on changed files. - -The Workers Vitest pool works by running code inside a Cloudflare Worker that Vitest would usually run inside a [Node.js Worker thread](https://nodejs.org/api/worker_threads.html). To make this possible, the pool **automatically injects** the [`nodejs_compat`](/workers/configuration/compatibility-flags/#nodejs-compatibility-flag), [`no_nodejs_compat_v2`] and [`export_commonjs_default`](/workers/configuration/compatibility-flags/#commonjs-modules-do-not-export-a-module-namespace) compatibility flags. This is the minimal compatibility setup that still allows Vitest to run correctly, but without pulling in polyfills and globals that aren't required. If you already have a Node.js compatibility flag defined in your configuration, Vitest Pool Workers will not try to add those flags. - -:::caution - -Using Vitest Pool Workers may cause your Worker to behave differently when deployed than during testing as the `nodejs_compat` flag is enabled by default. This means that Node.js-specific APIs and modules are available when running your tests. However, Cloudflare Workers do not support these Node.js APIs in the production environment unless you specify this flag in your Worker configuration. - -If you do not have a `nodejs_compat` or `nodejs_compat_v2` flag in your configuration and you import a Node.js module in your Worker code, your tests may pass, but you will find that you will not be able to deploy this Worker, as the upload call (either via the REST API or via Wrangler) will throw an error. +Each Worker has its own module evaluation cache. This cache contains evaluated module instances and module-level state. -However, if you use Node.js globals that are not supported by the runtime, your Worker upload will be successful, but you may see errors in production code. Let's create a contrived example to illustrate the issue. +As Workers are reused between test runs, their evaluation caches are also reused. Vitest invalidates parts of each cache based on changed files. -The Wrangler configuration file does not specify either `nodejs_compat` or `nodejs_compat_v2`: +Compiled artifacts are separate from module evaluation caches. The [new module registry](/workers/reference/module-registry/#reuse-compiled-artifacts) can share compiled artifacts across isolate replicas without sharing evaluated state. - -```jsonc -{ "name": "test", - "main": "src/index.ts", - "compatibility_date": "$today" - # no nodejs_compat flags here -} -``` - +The Workers Vitest pool runs code inside a Cloudflare Worker that Vitest would usually run inside a [Node.js Worker thread](https://nodejs.org/api/worker_threads.html). To support Vitest, the pool ensures that `nodejs_compat_v2` is active. -In our `src/index.ts` file, we use the `process` object, which is a Node.js global, unavailable in the Workerd runtime: +With the legacy module registry, it also ensures that [`export_commonjs_default`](/workers/configuration/compatibility-flags/#commonjs-modules-do-not-export-a-module-namespace) is active. The pool may override conflicting compatibility flags. -```typescript -export default { - async fetch(request, env, ctx): Promise { - process.env.TEST = "test"; - return new Response(process.env.TEST); - }, -} satisfies ExportedHandler; -``` - -The test is a simple assertion that the Worker managed to use `process`. - -```typescript -it('responds with "test"', async () => { - const response = await exports.default.fetch("https://example.com/"); - expect(await response.text()).toMatchInlineSnapshot(`"test"`); -}); -``` - -Now, if we run `npm run test`, we see that the tests will _pass_: - -``` - ✓ test/index.spec.ts (1) - ✓ responds with "test" +:::caution - Test Files 1 passed (1) - Tests 1 passed (1) -``` +For compatibility dates before `2026-08-04`, tests can access Node.js APIs that are unavailable to the deployed Worker. Add `nodejs_compat` to your Worker configuration, or avoid those APIs. -And we can run `wrangler dev` and `wrangler deploy` without issues. It _looks like_ our code is fine. However, this code will fail in production as `process` is not available in the Workerd runtime. +For compatibility dates on or after `2026-08-04`, deployed Workers enable Node.js compatibility by default. You can still turn it off with compatibility flags. -To fix the issue, we either need to avoid using Node.js APIs, or add the `nodejs_compat` flag to our Wrangler configuration. +For configuration details, refer to [Node.js compatibility](/workers/runtime-apis/nodejs/). ::: diff --git a/src/content/docs/workers/testing/vitest-integration/known-issues.mdx b/src/content/docs/workers/testing/vitest-integration/known-issues.mdx index 7f4cc9f78dd..3f871fc9476 100644 --- a/src/content/docs/workers/testing/vitest-integration/known-issues.mdx +++ b/src/content/docs/workers/testing/vitest-integration/known-issues.mdx @@ -128,6 +128,12 @@ export default defineConfig({ You can find an example in the [Recipes](/workers/testing/vitest-integration/recipes) page. +### New module registry + +The Workers Vitest integration does not support the [`new_module_registry`](/workers/reference/module-registry/) compatibility flag. + +Run tests with the legacy module registry. + ### Importing modules from global setup file Although Vitest is set up to resolve packages for the [`workerd`](https://github.com/cloudflare/workerd) runtime, it runs your global setup file in the Node.js environment. This can cause issues when importing packages like [Postgres.js](https://github.com/cloudflare/workers-sdk/issues/6465), which exports a non-Node version for `workerd`. diff --git a/src/content/docs/workers/tutorials/build-a-qr-code-generator.mdx b/src/content/docs/workers/tutorials/build-a-qr-code-generator.mdx index 965c1e3b4d5..7358c10631c 100644 --- a/src/content/docs/workers/tutorials/build-a-qr-code-generator.mdx +++ b/src/content/docs/workers/tutorials/build-a-qr-code-generator.mdx @@ -63,7 +63,7 @@ When a Worker receives a `fetch` event, the Worker returns the newly constructed ## 2. Handle Incoming Request -Any project you publish to Cloudflare Workers can make use of modern JavaScript tooling like ES modules, `npm` packages, and [`async`/`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) functions to build your application. In addition to writing Workers, you can use Workers to [build full applications](/workers/tutorials/build-a-slackbot/) using the same tooling and process as in this tutorial. +Workers projects can use ES modules, compatible `npm` packages, and [`async`/`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) functions. You can also use Workers to [build full applications](/workers/tutorials/build-a-slackbot/) with the same process. The QR code generator you will build in this tutorial will be a Worker that runs on a single route and receives requests. Each request will contain a text message (a URL, for example), which the function will encode into a QR code. The function will then respond with the QR code in PNG image format. @@ -125,7 +125,9 @@ export default { ## 3. Build a QR code generator -All projects deployed to Cloudflare Workers support npm packages. This support makes it easy to rapidly build out functionality in your Workers. The ['qrcode-svg'](https://github.com/papnkukn/qrcode-svg) package is a great way to take text and encode it into a QR code. In the command line, install and save 'qrcode-svg' to your project’s 'package.json': +Workers can use npm packages that rely on [supported runtime APIs](/workers/runtime-apis/). Packages requiring unsupported APIs may not work. + +The [`qrcode-svg`](https://github.com/papnkukn/qrcode-svg) package converts text into QR codes. Install `qrcode-svg` and save it to your project's `package.json`: diff --git a/src/content/docs/workers/vite-plugin/get-started.mdx b/src/content/docs/workers/vite-plugin/get-started.mdx index 305a4f2fcf9..9dcb1b8d399 100644 --- a/src/content/docs/workers/vite-plugin/get-started.mdx +++ b/src/content/docs/workers/vite-plugin/get-started.mdx @@ -40,6 +40,8 @@ Ensure that you include `"type": "module"` in order to use ES modules by default +The plugin supports Vite 6.1, 7, and 8. Vite 8 is not required. + ## Create your Vite config file and include the Cloudflare plugin ```ts title="vite.config.ts" @@ -55,6 +57,8 @@ The Cloudflare Vite plugin doesn't require any configuration by default and will Refer to the [API reference](/workers/vite-plugin/reference/api/) for configuration options. +Vite handles installed npm package resolution and processes source modules. A package must resolve successfully and use APIs available in Workers. + ## Create your Worker config file @@ -76,6 +80,12 @@ The `main` field specifies the entry file for your Worker code. For more information about the Worker configuration, see [Configuration](/workers/wrangler/configuration/). +During `vite build`, Vite transforms source modules into JavaScript output chunks. With Vite 8, Rolldown emits these chunks. + +The plugin generates a `no_bundle` Wrangler configuration for the output. Wrangler then uploads the chunks as separate runtime modules without rebuilding them. + +The Workers [module registry](/workers/reference/module-registry/) loads these runtime modules. You can use registry features with modules from other supported build tools. + ## Create your Worker entry file ```ts title="src/index.ts" diff --git a/src/content/docs/workers/vite-plugin/index.mdx b/src/content/docs/workers/vite-plugin/index.mdx index b2e15669d52..41a519e4163 100644 --- a/src/content/docs/workers/vite-plugin/index.mdx +++ b/src/content/docs/workers/vite-plugin/index.mdx @@ -8,18 +8,27 @@ products: - workers --- -The Cloudflare Vite plugin enables a full-featured integration between [Vite](https://vite.dev/) and the [Workers runtime](/workers/runtime-apis/). -Your Worker code runs inside [workerd](https://github.com/cloudflare/workerd), matching the production behavior as closely as possible and providing confidence as you develop and deploy your applications. +The Cloudflare Vite plugin integrates [Vite](https://vite.dev/) with the [Workers runtime](/workers/runtime-apis/). +Your Worker code runs inside [workerd](https://github.com/cloudflare/workerd) during local development. ## Features - Uses the Vite [Environment API](https://vite.dev/guide/api-environment) to integrate Vite with the Workers runtime - Provides direct access to [Workers runtime APIs](/workers/runtime-apis/) and [bindings](/workers/runtime-apis/bindings/) +- Handles installed npm package resolution through Vite's build pipeline - Builds your front-end assets for deployment to Cloudflare, enabling you to build static sites, SPAs, and full-stack applications - Official support for [TanStack Start](https://tanstack.com/start/) and [React Router v8](https://reactrouter.com/) with server-side rendering - Leverages Vite's hot module replacement for consistently fast updates - Supports `vite preview` for previewing your build output in the Workers runtime prior to deployment +The plugin supports Vite 6.1, 7, and 8. You do not need Vite 8 to use the plugin. + +With Vite 8, Rolldown can emit multiple JavaScript output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. + +The Workers [module registry](/workers/reference/module-registry/) loads deployed runtime modules. Its behavior applies regardless of which supported build tool produced those modules. + +Vite package resolution does not guarantee runtime compatibility. A package must use APIs supported by the Workers runtime. + ## Use cases - [TanStack Start](https://tanstack.com/start/) diff --git a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx index c96e717fc26..433197d12c4 100644 --- a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx +++ b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx @@ -14,7 +14,17 @@ There are a few key differences to highlight: ## Input and output Worker config files With the Cloudflare Vite plugin, your [Worker config file](/workers/wrangler/configuration/) (for example, `wrangler.jsonc`) is the input configuration and a separate output configuration is created as part of the build. -This output file is a snapshot of your configuration at the time of the build and is modified to reference your build artifacts. +Vite transforms your source modules into JavaScript output chunks. With Vite 8, Rolldown emits these chunks. + +The output configuration references the entry chunk and sets `no_bundle` to `true`. It also includes rules that let Wrangler upload the other JavaScript chunks as separate runtime modules. + +This generated setting prevents Wrangler from rebuilding Vite's output. Vite already handles transpilation and npm package resolution before deployment. + +The plugin also applies configured Node.js compatibility polyfills. + +The Workers [module registry](/workers/reference/module-registry/) loads these runtime modules. The registry also works with separate modules produced by other build tools. + +The output file is a snapshot of your configuration at build time. It is the configuration that is used for preview and deployment. Once you have run `vite build`, running `wrangler deploy` or `vite preview` will automatically locate this output configuration file. @@ -40,6 +50,8 @@ The following build-related options are handled by Vite and are not applicable w - `base_dir` - `preserve_file_names` +The input `no_bundle` option is ignored because the plugin always builds source with Vite. The generated output configuration uses `no_bundle` only to stop Wrangler from processing the resulting chunks again. + ### Not supported - `site` — Use [Workers Assets](/workers/static-assets/) instead. diff --git a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx index 6ebab36c788..02e7b282273 100644 --- a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx +++ b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx @@ -65,6 +65,44 @@ For more information about Vite's configuration options, see [Configuring Vite]( The default behavior of using the Worker name as the environment name is appropriate when you have a standalone Worker, such as an API that is accessed from your front-end application, or an [auxiliary Worker](/workers/vite-plugin/reference/api/#interface-pluginconfig) that is accessed via service bindings. +## Troubleshoot package resolution + +With Vite 8, the plugin sets Rolldown's `platform` to `"neutral"` for Worker environments. Keep this default unless a specific package requires Node-targeted transformations. + +For that package-specific workaround, add a plugin that overrides both build and dependency optimization settings after the Cloudflare plugin applies its defaults: + +```ts title="vite.config.ts" +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare(), + { + name: "worker-node-platform", + configEnvironment(name) { + if (name !== "my_worker") { + return; + } + + return { + build: { + rolldownOptions: { platform: "node" }, + }, + optimizeDeps: { + rolldownOptions: { platform: "node" }, + }, + }; + }, + }, + ], +}); +``` + +This environment-wide override changes how Rolldown resolves and transforms modules in the selected Worker environment. It does not add Node.js APIs to the Workers runtime. + +Use [`nodejs_compat`](/workers/runtime-apis/nodejs/) for supported Node.js APIs and polyfills. The override does not preserve every CommonJS module boundary or guarantee package compatibility. + ## Full-stack frameworks If you are using the Cloudflare Vite plugin with [TanStack Start](https://tanstack.com/start/) or [React Router v8](https://reactrouter.com/), then your Worker is used for server-side rendering and tightly integrated with the framework. diff --git a/src/content/docs/workers/wrangler/bundling.mdx b/src/content/docs/workers/wrangler/bundling.mdx index 47e554d7f4a..db8eaa0cd80 100644 --- a/src/content/docs/workers/wrangler/bundling.mdx +++ b/src/content/docs/workers/wrangler/bundling.mdx @@ -9,7 +9,11 @@ products: import { PackageManagers } from "~/components"; -By default, Wrangler bundles your Worker code using [`esbuild`](https://esbuild.github.io/). This means that Wrangler has built-in support for importing modules from [npm](https://www.npmjs.com/) defined in your `package.json`. To review the exact code that Wrangler will upload to Cloudflare, run `npx wrangler deploy --dry-run --outdir dist`, which will show your Worker code after Wrangler's bundling. +By default, Wrangler uses [`esbuild`](https://esbuild.github.io/) to resolve and bundle your source modules. `esbuild` also attempts to resolve installed [npm](https://www.npmjs.com/) package imports. + +Wrangler produces one JavaScript entry module by default. An npm package works only if `esbuild` can resolve it and its built code uses APIs available in Workers. + +To inspect the uploaded code, run `npx wrangler deploy --dry-run --outdir dist`. This command writes Wrangler's build output to `dist`.
`esbuild` version @@ -25,13 +29,25 @@ You can provide `rules` and set `find_additional_modules` in your configuration Furthermore, we have an escape hatch in the form of [Custom Builds](/workers/wrangler/custom-builds/), which lets you run your own build before Wrangler's built-in one. ::: +## Understand module stages + +These terms describe different stages of a Worker build: + +- **Source module**: A file or package module before the build runs. +- **Output chunk**: A JavaScript file emitted by a build tool. One chunk can contain many source modules. +- **Runtime module**: A deployed module that the Workers [module registry](/workers/reference/module-registry/) can load. + +Wrangler usually converts many source modules into one entry output chunk. Wrangler then uploads that chunk as the entry runtime module. + +You can also upload external modules beside the entry module. Wrangler does not inline those modules into the entry output. + ## Including non-JavaScript modules {/* Changes to this section should also be applied to /workers/vite-plugin/reference/non-javascript-modules */} -Bundling your Worker code takes multiple modules and bundles them into one file. +Bundling takes multiple source modules and creates one JavaScript output chunk by default. Sometimes, you might have modules that cannot be inlined directly into the bundle. -For example, instead of bundling a Wasm file into your JavaScript Worker, you would want to upload the Wasm file as a separate module that can be imported at runtime. +For example, you can upload a WebAssembly (Wasm) file as a separate runtime module. Wrangler supports this by default for the following file types: | Module extension | Imported type | @@ -75,7 +91,7 @@ Cloudflare Workers does not support `WebAssembly.instantiateStreaming()`. ## Find additional modules By setting `find_additional_modules` to `true` in your configuration file, Wrangler will traverse the file tree below `base_dir`. -Any files that match the `rules` you define will also be included as unbundled, external modules in the deployed Worker. +Wrangler also includes files that match your `rules` as unbundled, external runtime modules. This approach is useful for supporting lazy loading of large or dynamically imported JavaScript files: @@ -83,7 +99,9 @@ This approach is useful for supporting lazy loading of large or dynamically impo If matching rule is added to `rules`, then this file would only be loaded and executed at runtime when it is actually imported. - Previously, variable based dynamic imports (for example, ``await import(`./lang/${language}.mjs`)``) would always fail at runtime because Wrangler had no way of knowing which modules to include in the upload. Providing a rule that matches all these files, such as `{ "type": "EsModule", "globs": ["./lang/**/*.mjs"], "fallthrough": true }`, will ensure this module is available at runtime. -- "Partial bundling" is supported when `find_additional_modules` is `true`, and a source file matches one of the configured `rules`, since Wrangler will then treat it as "external" and not try to bundle it into the entry-point file. +- "Partial bundling" is supported when `find_additional_modules` is `true`, and a source file matches one of the configured `rules`. Wrangler treats the matching file as external and uploads it as a separate runtime module. + +External modules must already contain code that the Workers runtime can execute. Wrangler does not process them as part of the entry module bundle. ## `NODE_ENV` @@ -124,13 +142,20 @@ Wrangler respects the [conditional `exports` field](https://nodejs.org/api/packa Disabling bundling is not recommended in most scenarios. Use this option only when deploying code pre-processed by other tooling. ::: -If your build tooling already produces build artifacts suitable for direct deployment to Cloudflare, you can opt out of bundling by using the `--no-bundle` command line flag: `npx wrangler deploy --no-bundle`. If you opt out of bundling, Wrangler will not process your code and some features introduced by Wrangler bundling (for example minification, and polyfills injection) will not be available. +If another build tool produces deployment-ready files, use `npx wrangler deploy --no-bundle`. Wrangler then skips transpilation, npm package resolution, minification, and polyfill injection. + +Your output must include the entry module and every application module the Worker can import. Configure [`rules`](/workers/wrangler/configuration/#bundling) so Wrangler includes each module type in the deployment. Use [Custom Builds](/workers/wrangler/custom-builds/) to customize what Wrangler will bundle and upload to the Cloudflare global network when you use [`wrangler dev`](/workers/wrangler/commands/general/#dev) and [`wrangler deploy`](/workers/wrangler/commands/general/#deploy). ## Generated Wrangler configuration -Some framework tools, or custom pre-build processes, generate a modified Wrangler configuration to be used to deploy the Worker code. -It is possible for Wrangler to automatically use this generated configuration rather than the original, user's configuration. +Some framework tools or custom build processes generate a modified Wrangler configuration. Wrangler can use this generated configuration instead of your original configuration. + +The Cloudflare Vite plugin uses this workflow for every supported Vite version. With Vite 8, Rolldown emits JavaScript output chunks. The plugin generates a `no_bundle` configuration that uploads those chunks as separate runtime modules without rebuilding them. + +The generated `no_bundle` setting applies only after Vite processes the source. Vite handles transpilation and package resolution before Wrangler uploads the output. + +The plugin also applies configured Node.js compatibility polyfills during the build. -See [Generated Wrangler configuration](/workers/wrangler/configuration/#generated-wrangler-configuration) for more information. +For more information, refer to [Generated Wrangler configuration](/workers/wrangler/configuration/#generated-wrangler-configuration). diff --git a/src/content/docs/workers/wrangler/commands/workers.mdx b/src/content/docs/workers/wrangler/commands/workers.mdx index 8be228d4b32..890bd4a7dea 100644 --- a/src/content/docs/workers/wrangler/commands/workers.mdx +++ b/src/content/docs/workers/wrangler/commands/workers.mdx @@ -64,7 +64,8 @@ None of the options for this command are required. Many of these options can be - Path(s) to [Wrangler configuration file](/workers/wrangler/configuration/). If not provided, Wrangler will use the nearest config file based on your current working directory. - You can provide multiple configuration files to run multiple Workers in one dev session like this: `wrangler dev -c ./wrangler.toml -c ../other-worker/wrangler.toml`. The first config will be treated as the _primary_ Worker, which will be exposed over HTTP. The remaining config files will only be accessible via a service binding from the primary Worker. - `--no-bundle` - - Skip Wrangler's build steps. Particularly useful when using custom builds. Refer to [Bundling](/workers/wrangler/bundling/) for more information. + - Skip Wrangler's transpilation, npm package resolution, bundling, minification, and polyfill injection. + - Provide the entry module and every application module the Worker can import. Refer to [Bundling](/workers/wrangler/bundling/#disable-bundling). - `--env` - Perform on a specific environment. - `--compatibility-date` @@ -186,7 +187,8 @@ None of the options for this command are required. Also, many can be set in your - `--name` - Name of the Worker. - `--no-bundle` - - Skip Wrangler's build steps. Particularly useful when using custom builds. Refer to [Bundling](/workers/wrangler/bundling/) for more information. + - Skip Wrangler's transpilation, npm package resolution, bundling, minification, and polyfill injection. + - Provide the entry module and every application module the Worker can import. Refer to [Bundling](/workers/wrangler/bundling/#disable-bundling). - `--env` - Perform on a specific environment. diff --git a/src/content/docs/workers/wrangler/configuration.mdx b/src/content/docs/workers/wrangler/configuration.mdx index 8f863c4f470..61e1f607ccc 100644 --- a/src/content/docs/workers/wrangler/configuration.mdx +++ b/src/content/docs/workers/wrangler/configuration.mdx @@ -182,7 +182,8 @@ The `main` key is optional for assets-only Workers. - Configures a custom build step to be run by Wrangler when building your Worker. Refer to [Custom builds](#custom-builds). - Not applicable if you're using the [Cloudflare Vite plugin](/workers/vite-plugin/). - `no_bundle` - - Skip internal build steps and directly deploy your Worker script. You must have a plain JavaScript Worker with no dependencies. + - Skip Wrangler's transpilation, npm package resolution, bundling, minification, and polyfill injection. + - Use this option only with deployment-ready JavaScript from another build tool. Include the entry module and every application module the Worker can import. - Not applicable if you're using the [Cloudflare Vite plugin](/workers/vite-plugin/). - `find_additional_modules` - If true then Wrangler will traverse the file tree below `base_dir`. @@ -1408,13 +1409,16 @@ Wrangler bundling is not applicable if you're using the [Cloudflare Vite plugin] ::: Wrangler can operate in two modes: the default bundling mode and `--no-bundle` mode. -In bundling mode, Wrangler will traverse all the imports of your code and generate a single JavaScript "entry-point" file. -Imported source code is "inlined/bundled" into this entry-point file. +In bundling mode, Wrangler traverses your source module imports. It uses `esbuild` to resolve package imports and generate one JavaScript entry module by default. + +In `--no-bundle` mode, Wrangler uploads deployment-ready modules without rebuilding them. Wrangler does not transpile source, resolve npm packages, or inject polyfills in this mode. It is also possible to include additional modules into your Worker, which are uploaded alongside the entry-point. You specify which additional modules should be included into your Worker using the `rules` key, making these modules available to be imported when your Worker is invoked. The `rules` key will be an array of the below object. +These uploaded files become runtime modules. The Workers [module registry](/workers/reference/module-registry/) resolves imports between them. + - `type` - The type of module. Must be one of: `ESModule`, `CommonJS`, `CompiledWasm`, `Text` or `Data`. - `globs` @@ -1461,6 +1465,8 @@ By setting `find_additional_modules` to `true` in your configuration file, Wrang Any files that match `rules` will also be included as unbundled, external modules in the deployed Worker. `base_dir` defaults to the directory containing your `main` entrypoint. +External modules must already use syntax and APIs supported by Workers. Wrangler does not process them as part of the entry bundle. + See https://developers.cloudflare.com/workers/wrangler/bundling/ for more details and examples. ### Python Workers @@ -1754,6 +1760,10 @@ For example, when using the [Cloudflare Vite plugin](/workers/vite-plugin/), an ::: +Vite transforms source modules into JavaScript output chunks before generating this configuration. With Vite 8, Rolldown emits these chunks, and the plugin configures Wrangler to upload them as separate runtime modules with `no_bundle` enabled. + +The generated setting prevents Wrangler from rebuilding Vite's output. It does not skip Vite's transpilation, npm package resolution, or configured polyfills. + Some framework tools, or custom pre-build processes, generate a modified Wrangler configuration to be used to deploy the Worker code. In this case, the tool may also create a special `.wrangler/deploy/config.json` file that redirects Wrangler to use the generated configuration rather than the original, user's configuration. diff --git a/src/content/docs/workers/wrangler/custom-builds.mdx b/src/content/docs/workers/wrangler/custom-builds.mdx index 0b63ae3bd2a..1ac6fae9876 100644 --- a/src/content/docs/workers/wrangler/custom-builds.mdx +++ b/src/content/docs/workers/wrangler/custom-builds.mdx @@ -13,7 +13,7 @@ Custom builds are a way for you to customize how your code is compiled, before b :::note -Wrangler runs [esbuild](https://esbuild.github.io/) by default as part of the `dev` and `deploy` commands, and bundles your Worker project into a single Worker script. Refer to [Bundling](/workers/wrangler/bundling/). +Wrangler runs [esbuild](https://esbuild.github.io/) by default with the `dev` and `deploy` commands. It produces one JavaScript entry module by default. Refer to [Bundling](/workers/wrangler/bundling/). ::: ## Configure custom builds @@ -48,6 +48,14 @@ Example: +## Deploy build output + +Wrangler normally processes your custom build output with `esbuild`. It resolves imports and bundles source modules into the entry module. + +Use `no_bundle` only when your custom build produces deployment-ready JavaScript. In this mode, Wrangler does not transpile code, resolve npm packages, or inject polyfills. + +Include the entry module and every application module the Worker can import. Configure [module rules](/workers/wrangler/configuration/#bundling) for files that Wrangler must upload separately. + ## `WRANGLER_COMMAND` environment variable When Wrangler runs your custom build command, it sets the `WRANGLER_COMMAND` environment variable so your build script can detect which Wrangler command triggered the build. This allows you to customize the build process based on the deployment context. diff --git a/src/content/partials/workers/source-maps.mdx b/src/content/partials/workers/source-maps.mdx index 5d7408d7a0a..5d7d0385245 100644 --- a/src/content/partials/workers/source-maps.mdx +++ b/src/content/partials/workers/source-maps.mdx @@ -6,4 +6,6 @@ Most JavaScript code is first bundled, often transpiled, and then minified before being deployed to production. This process creates smaller bundles to optimize performance and converts code from TypeScript to Javascript if needed. -Source maps translate compiled and minified code back to the original code that you wrote. Source maps are combined with the stack trace returned by the JavaScript runtime to present you with a stack trace. +Stack frames can identify deployed output with URLs such as `file:///bundle/index.js`. These URLs identify uploaded runtime modules, not your original source files. For details, refer to the [module registry reference](/workers/reference/module-registry/). + +Source maps translate compiled and minified output back to your original code. Cloudflare combines source maps with runtime stack traces to show original file names and line numbers. From 45f3c48b9c06742ad781cbc64018fc2d5b9d396f Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sat, 15 Aug 2026 17:12:18 -0700 Subject: [PATCH 3/4] [Workers] Clarify module registry concepts --- .../workers/2026-08-05-new-module-registry.mdx | 2 +- .../compatibility-flags/new-module-registry.md | 2 +- src/content/docs/dynamic-workers/api-reference.mdx | 4 ++-- .../examples/dynamic-workers-playground.mdx | 2 +- .../configuration/multipart-upload-metadata.mdx | 2 +- .../workers/local-development/wrangler-vs-vite.mdx | 2 +- src/content/docs/workers/observability/errors.mdx | 2 +- .../docs/workers/platform/infrastructure-as-code.mdx | 2 +- src/content/docs/workers/platform/limits.mdx | 4 ++-- .../docs/workers/reference/module-registry.mdx | 12 ++++++------ .../workers/runtime-apis/bindings/worker-loader.mdx | 4 ++-- .../docs/workers/runtime-apis/webassembly/index.mdx | 2 +- .../vitest-integration/isolation-and-concurrency.mdx | 2 +- src/content/docs/workers/vite-plugin/get-started.mdx | 2 +- src/content/docs/workers/vite-plugin/index.mdx | 2 +- .../reference/migrating-from-wrangler-dev.mdx | 2 +- .../vite-plugin/reference/vite-environments.mdx | 4 ++-- src/content/docs/workers/wrangler/bundling.mdx | 2 +- src/content/docs/workers/wrangler/configuration.mdx | 2 +- 19 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/content/changelog/workers/2026-08-05-new-module-registry.mdx b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx index 695dfc14db1..14c553b8517 100644 --- a/src/content/changelog/workers/2026-08-05-new-module-registry.mdx +++ b/src/content/changelog/workers/2026-08-05-new-module-registry.mdx @@ -15,7 +15,7 @@ Key changes include: - **Module loading** — Validate import attributes and follow Node.js `require(esm)` behavior. - **Consistent errors** — Use the same error classes across loading paths. - **Lazy compilation** — Compile modules when they are first imported. -- **Compiled artifacts** — Reuse cached compilation data when available. +- **Compilation reuse** — Reuse cached compilation data when available. Dynamic WebAssembly source phase imports through `import.source()` require the new registry. Static `import source` works with both registries. diff --git a/src/content/compatibility-flags/new-module-registry.md b/src/content/compatibility-flags/new-module-registry.md index bd6f656fbce..c7f14bcf2bc 100644 --- a/src/content/compatibility-flags/new-module-registry.md +++ b/src/content/compatibility-flags/new-module-registry.md @@ -19,7 +19,7 @@ When enabled, the new registry provides: - URL-based specifiers with query and fragment identities - Import attribute validation and Node.js `require(esm)` behavior - Consistent errors across module loading paths -- Lazy compilation and reusable compiled artifacts +- Compilation on first import and reuse of compilation data - Dynamic WebAssembly source phase imports Static `import source` works with both registries. Dynamic `import.source()` requires the new registry. diff --git a/src/content/docs/dynamic-workers/api-reference.mdx b/src/content/docs/dynamic-workers/api-reference.mdx index ce913ad4940..874cef60ff6 100644 --- a/src/content/docs/dynamic-workers/api-reference.mdx +++ b/src/content/docs/dynamic-workers/api-reference.mdx @@ -40,7 +40,7 @@ You could, for example, use IDs of the form `:`, wh It is never guaranteed that two requests will go to the same isolate. Even if you use the same `WorkerStub` to make multiple requests, they could execute in different isolates. The callback passed to `loader.get()` could be called any number of times (although it is unusual for it to be called more than once). -Loader caching and module caching are separate. Each isolate has its own module evaluation cache and evaluates each module instance once. +Loader caching and module evaluation caching are separate. The loader can reuse code or a warm isolate by ID. Each isolate evaluates and caches its own module instances. When local development enables the [new module registry](/workers/reference/module-registry/), compiled JavaScript and WebAssembly artifacts can be shared across isolate replicas. Module-level state remains local to each isolate. @@ -70,7 +70,7 @@ The runtime identifier for the Worker's main module. This must match one of the A dictionary object mapping runtime module identifiers to their contents. These identifiers name uploaded runtime modules, not original source files. -In local development with the [new module registry](/workers/reference/module-registry/), ordinary JavaScript module identifiers are canonicalized under `file:///bundle/`. Imports resolve against that registry, and URL specifiers do not fetch network resources. +In local development with the [new module registry](/workers/reference/module-registry/), the identifier `index.js` maps to `file:///bundle/index.js`. Imports resolve against that registry, and URL specifiers do not fetch network resources. Use ordinary module names unless the upload API documents support for URL-shaped names. These rules do not describe Python import resolution. diff --git a/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx b/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx index 04f83c88074..acd9363d9a0 100644 --- a/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx +++ b/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx @@ -80,7 +80,7 @@ const response = await worker.getEntrypoint().fetch(request); In the playground, a cache hit can skip the build and load phases. Reuse is not guaranteed, so a later request can still create a new isolate or call the code callback again. -Loader reuse is separate from module caching. Each isolate retains its own module-level state. +Reusing a Worker ID does not guarantee reuse of module-level state. Each isolate retains its own state. ## Observability with Tail Workers diff --git a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx index 9a0367a4a35..f6115f4c25b 100644 --- a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx +++ b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx @@ -35,7 +35,7 @@ If you're using the [Workers Script Upload API](/api/resources/workers/subresour Each multipart file part name is a runtime module identifier. The `main_module` value selects one of those identifiers as the entrypoint. Module identifiers name uploaded runtime modules or output chunks, not original source files. -For JavaScript Workers using the [new module registry](/workers/reference/module-registry/), ordinary identifiers are canonicalized under `file:///bundle/`. Imports link against matching uploaded registry entries. +For JavaScript Workers using the [new module registry](/workers/reference/module-registry/), the identifier `main.js` maps to `file:///bundle/main.js`. Imports link against matching uploaded registry entries. URL specifiers resolve through the registry and do not fetch network resources. Use ordinary module names unless the upload API documents support for URL-shaped names. diff --git a/src/content/docs/workers/local-development/wrangler-vs-vite.mdx b/src/content/docs/workers/local-development/wrangler-vs-vite.mdx index a3c4e0f4f31..c20f2fe9404 100644 --- a/src/content/docs/workers/local-development/wrangler-vs-vite.mdx +++ b/src/content/docs/workers/local-development/wrangler-vs-vite.mdx @@ -37,7 +37,7 @@ Use [`wrangler dev`](/workers/wrangler/commands/general/#dev) when your project Both tools handle installed npm package resolution during a build. Resolution and runtime support vary by package. -The Vite plugin supports Vite 6.1, 7, and 8. Vite 8 uses Rolldown, which can emit multiple output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. +The Vite plugin supports Vite 6.1, 7, and 8. Vite 8 uses [Rolldown](https://rolldown.rs/), which can emit multiple output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. The Workers [module registry](/workers/reference/module-registry/) loads deployed runtime modules. Its features do not require Vite and apply to modules produced by other build tools. diff --git a/src/content/docs/workers/observability/errors.mdx b/src/content/docs/workers/observability/errors.mdx index 49148ad62a7..db8a053e724 100644 --- a/src/content/docs/workers/observability/errors.mdx +++ b/src/content/docs/workers/observability/errors.mdx @@ -224,7 +224,7 @@ This means that you are doing work in the top-level scope of your Worker that al ### Module loading errors -With the [new module registry](/workers/reference/module-registry/), errors in the statically reached startup graph appear during upload validation. A separately uploaded lazy module compiles on first import. Its errors can instead appear during the invocation that first imports it. +With the [new module registry](/workers/reference/module-registry/), upload validation checks the entrypoint and modules reached through static imports. A separately uploaded lazy module compiles on first import. Its errors can instead appear during the invocation that first imports it. A missing module throws an `Error`. An invalid specifier or unsupported import attribute throws a `TypeError`. The registry uses consistent resolution errors across static `import`, dynamic `import()`, and `require()`. diff --git a/src/content/docs/workers/platform/infrastructure-as-code.mdx b/src/content/docs/workers/platform/infrastructure-as-code.mdx index 2a9efc384d4..682bab1dc84 100644 --- a/src/content/docs/workers/platform/infrastructure-as-code.mdx +++ b/src/content/docs/workers/platform/infrastructure-as-code.mdx @@ -34,7 +34,7 @@ When using Wrangler for building and a different method for uploading, make sure Bundlers can emit an entrypoint and additional runtime modules or chunks. Upload every output with its generated name and content type. For multipart uploads, preserve each output as a separate part and set `main_module` to the generated entrypoint name. Uploading only the entrypoint can break imports. -Module names are runtime identifiers. For details about URL canonicalization and linking, refer to the [module registry reference](/workers/reference/module-registry/). +Module names are runtime identifiers. For details about how the registry converts names to URLs and matches imports, refer to the [module registry reference](/workers/reference/module-registry/). ::: diff --git a/src/content/docs/workers/platform/limits.mdx b/src/content/docs/workers/platform/limits.mdx index e4d8674ae1d..4bcfabf8655 100644 --- a/src/content/docs/workers/platform/limits.mdx +++ b/src/content/docs/workers/platform/limits.mdx @@ -288,9 +288,9 @@ To reduce Worker size: | ------------ | -------- | | Startup time | 1 second | -A Worker must process its startup module graph and execute global scope within 1 second. Larger startup graphs and expensive top-level initialization increase startup time. +A Worker must process its startup code and execute global scope within 1 second. Larger startup workloads and expensive top-level initialization increase startup time. -With the [new module registry](/workers/reference/module-registry/), this startup graph includes modules reached through static imports. Separately uploaded modules first reached by a later dynamic import compile on that first import. They do not add to initial startup time. +With the [new module registry](/workers/reference/module-registry/), the startup module graph contains the entrypoint and modules reached through static imports. Separately uploaded modules first reached by a later dynamic import compile on that first import. They do not add to initial startup time. When the platform rejects a deployment because the Worker exceeds the startup time limit, the validation returns the error `Script startup exceeded CPU time limit` (error code `10021`). Wrangler automatically generates a CPU profile that you can import into Chrome DevTools or open in VS Code. Refer to [`wrangler check startup`](/workers/wrangler/commands/workers/#startup) for more details. diff --git a/src/content/docs/workers/reference/module-registry.mdx b/src/content/docs/workers/reference/module-registry.mdx index e21ef400afb..ae385addd35 100644 --- a/src/content/docs/workers/reference/module-registry.mdx +++ b/src/content/docs/workers/reference/module-registry.mdx @@ -10,9 +10,9 @@ sidebar: import { TypeScriptExample, WranglerConfig } from "~/components"; -The Workers runtime uses a module registry to load modules. An experimental registry is available through a [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry). +The Workers runtime uses a module registry to load deployed modules referenced by `import` or `require()`. An experimental registry is available through a [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry). -The registry resolves module specifiers as URLs. It also provides module metadata, lazy compilation, and consistent loading behavior. +The experimental registry resolves module identifiers and specifiers as URLs. It also provides module metadata, compilation on first import, and consistent loading behavior. ## Configure the registry @@ -44,7 +44,7 @@ Do not set both flags. The new registry has no default date, so you must opt in. - Use [Cloudflare Vite plugin](/workers/vite-plugin/) 1.35.0 or later. - Vitest Pool Workers does not support the new registry. -These versions support the registry protocol. For local support for `import.meta.filename` and `import.meta.dirname`, use Wrangler 4.122.0 or later, or Cloudflare Vite plugin 1.52.0 or later. +For local support for `import.meta.filename` and `import.meta.dirname`, use Wrangler 4.122.0 or later, or Cloudflare Vite plugin 1.52.0 or later. Vite 8 does not currently bundle `import source` or `import.source()` syntax. Use Wrangler for the source phase import examples on this page. @@ -108,7 +108,7 @@ For example, an `https:` specifier in a deployed Worker only loads a matching up ### Distinguish queries and fragments -Queries and fragments form part of a bundle module identity. Different URLs create different module instances within an isolate. +Queries and fragments are part of a runtime module's URL identity. URLs with different queries or fragments create separate module instances within an [isolate](/workers/reference/how-workers-works/#isolates). This module stores state at the module top level: @@ -218,11 +218,11 @@ V8 evaluates each module instance once per isolate. Repeated imports return its Queries and fragments can create separate instances. Each of those instances evaluates separately. -### Reuse compiled artifacts +### Reuse compilation data Isolate replicas that share a registry can reuse V8 compilation data. WebAssembly modules can also reuse shared compiled artifacts. -Each isolate still creates and evaluates its own module instances. Cache reuse is an optimization and does not guarantee one compilation across all replicas. +Each isolate still creates and evaluates its own module instances. Compilation-data reuse is an optimization and does not guarantee one compilation across all replicas. ## Import WebAssembly source diff --git a/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx b/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx index cdcded5a20c..53650c96277 100644 --- a/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx +++ b/src/content/docs/workers/runtime-apis/bindings/worker-loader.mdx @@ -129,7 +129,7 @@ You could, for example, use IDs of the form `:`, wh It is never guaranteed that two requests will go to the same isolate. Even if you use the same `WorkerStub` to make multiple requests, they could execute in different isolates. The callback passed to `loader.get()` could be called any number of times (although it is unusual for it to be called more than once). -Loader caching and module caching are separate. Each isolate has its own module evaluation cache and evaluates each module instance once. +Loader caching and module evaluation caching are separate. The loader can reuse code or a warm isolate by ID. Each isolate evaluates and caches its own module instances. When local development enables the [new module registry](/workers/reference/module-registry/), compiled JavaScript and WebAssembly artifacts can be reused across isolate replicas. Module-level state remains local to each isolate. @@ -159,7 +159,7 @@ The runtime identifier for the Worker's main module. This must match one of the A dictionary object mapping runtime module identifiers to their contents. These identifiers name uploaded runtime modules, not original source files. -In local development with the [new module registry](/workers/reference/module-registry/), ordinary JavaScript identifiers are canonicalized under `file:///bundle/`. URL specifiers resolve through the registry and do not fetch network resources. +In local development with the [new module registry](/workers/reference/module-registry/), the identifier `index.js` maps to `file:///bundle/index.js`. URL specifiers resolve through the registry and do not fetch network resources. Use ordinary module names unless the upload API documents support for URL-shaped names. These rules do not describe Python import resolution. diff --git a/src/content/docs/workers/runtime-apis/webassembly/index.mdx b/src/content/docs/workers/runtime-apis/webassembly/index.mdx index 6a9c9a6a6ef..63ebb6839cb 100644 --- a/src/content/docs/workers/runtime-apis/webassembly/index.mdx +++ b/src/content/docs/workers/runtime-apis/webassembly/index.mdx @@ -32,7 +32,7 @@ WebAssembly standards include [many proposed APIs](https://webassembly.org/roadm ### SIMD -SIMD is supported on Workers. For more information on using SIMD in WebAssembly, refer to [Fast, parallel applications with WebAssembly SIMD](https://v8.dev/features/simd). +Single instruction, multiple data (SIMD) is supported on Workers. For more information on using SIMD in WebAssembly, refer to [Fast, parallel applications with WebAssembly SIMD](https://v8.dev/features/simd). ### Threads diff --git a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx index a4694324ff1..f0ff7283bea 100644 --- a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx +++ b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx @@ -35,7 +35,7 @@ Each Worker has its own module evaluation cache. This cache contains evaluated m As Workers are reused between test runs, their evaluation caches are also reused. Vitest invalidates parts of each cache based on changed files. -Compiled artifacts are separate from module evaluation caches. The [new module registry](/workers/reference/module-registry/#reuse-compiled-artifacts) can share compiled artifacts across isolate replicas without sharing evaluated state. +Compilation data is separate from module evaluation caches. The [new module registry](/workers/reference/module-registry/#reuse-compilation-data) can share compilation data across isolate replicas without sharing evaluated state. The Workers Vitest pool runs code inside a Cloudflare Worker that Vitest would usually run inside a [Node.js Worker thread](https://nodejs.org/api/worker_threads.html). To support Vitest, the pool ensures that `nodejs_compat_v2` is active. diff --git a/src/content/docs/workers/vite-plugin/get-started.mdx b/src/content/docs/workers/vite-plugin/get-started.mdx index 9dcb1b8d399..762914b396b 100644 --- a/src/content/docs/workers/vite-plugin/get-started.mdx +++ b/src/content/docs/workers/vite-plugin/get-started.mdx @@ -80,7 +80,7 @@ The `main` field specifies the entry file for your Worker code. For more information about the Worker configuration, see [Configuration](/workers/wrangler/configuration/). -During `vite build`, Vite transforms source modules into JavaScript output chunks. With Vite 8, Rolldown emits these chunks. +During `vite build`, Vite transforms source modules into JavaScript output chunks. With Vite 8, [Rolldown](https://rolldown.rs/) emits these chunks. The plugin generates a `no_bundle` Wrangler configuration for the output. Wrangler then uploads the chunks as separate runtime modules without rebuilding them. diff --git a/src/content/docs/workers/vite-plugin/index.mdx b/src/content/docs/workers/vite-plugin/index.mdx index 41a519e4163..b9c0c237d1f 100644 --- a/src/content/docs/workers/vite-plugin/index.mdx +++ b/src/content/docs/workers/vite-plugin/index.mdx @@ -23,7 +23,7 @@ Your Worker code runs inside [workerd](https://github.com/cloudflare/workerd) du The plugin supports Vite 6.1, 7, and 8. You do not need Vite 8 to use the plugin. -With Vite 8, Rolldown can emit multiple JavaScript output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. +With Vite 8, [Rolldown](https://rolldown.rs/) can emit multiple JavaScript output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration. The Workers [module registry](/workers/reference/module-registry/) loads deployed runtime modules. Its behavior applies regardless of which supported build tool produced those modules. diff --git a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx index 433197d12c4..465646fb08f 100644 --- a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx +++ b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx @@ -14,7 +14,7 @@ There are a few key differences to highlight: ## Input and output Worker config files With the Cloudflare Vite plugin, your [Worker config file](/workers/wrangler/configuration/) (for example, `wrangler.jsonc`) is the input configuration and a separate output configuration is created as part of the build. -Vite transforms your source modules into JavaScript output chunks. With Vite 8, Rolldown emits these chunks. +Vite transforms your source modules into JavaScript output chunks. With Vite 8, [Rolldown](https://rolldown.rs/) emits these chunks. The output configuration references the entry chunk and sets `no_bundle` to `true`. It also includes rules that let Wrangler upload the other JavaScript chunks as separate runtime modules. diff --git a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx index 02e7b282273..1ca40114381 100644 --- a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx +++ b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx @@ -67,7 +67,7 @@ The default behavior of using the Worker name as the environment name is appropr ## Troubleshoot package resolution -With Vite 8, the plugin sets Rolldown's `platform` to `"neutral"` for Worker environments. Keep this default unless a specific package requires Node-targeted transformations. +With Vite 8, the plugin sets [Rolldown's](https://rolldown.rs/) `platform` to `"neutral"` for Worker environments. Keep this default unless a specific package requires Node-targeted transformations. For that package-specific workaround, add a plugin that overrides both build and dependency optimization settings after the Cloudflare plugin applies its defaults: @@ -101,7 +101,7 @@ export default defineConfig({ This environment-wide override changes how Rolldown resolves and transforms modules in the selected Worker environment. It does not add Node.js APIs to the Workers runtime. -Use [`nodejs_compat`](/workers/runtime-apis/nodejs/) for supported Node.js APIs and polyfills. The override does not preserve every CommonJS module boundary or guarantee package compatibility. +Use [`nodejs_compat`](/workers/runtime-apis/nodejs/) for supported Node.js APIs and polyfills. The override does not guarantee that CommonJS modules remain separate during bundling. It also does not guarantee package compatibility. ## Full-stack frameworks diff --git a/src/content/docs/workers/wrangler/bundling.mdx b/src/content/docs/workers/wrangler/bundling.mdx index db8eaa0cd80..4a6bc804f91 100644 --- a/src/content/docs/workers/wrangler/bundling.mdx +++ b/src/content/docs/workers/wrangler/bundling.mdx @@ -152,7 +152,7 @@ Use [Custom Builds](/workers/wrangler/custom-builds/) to customize what Wrangler Some framework tools or custom build processes generate a modified Wrangler configuration. Wrangler can use this generated configuration instead of your original configuration. -The Cloudflare Vite plugin uses this workflow for every supported Vite version. With Vite 8, Rolldown emits JavaScript output chunks. The plugin generates a `no_bundle` configuration that uploads those chunks as separate runtime modules without rebuilding them. +The Cloudflare Vite plugin uses this workflow for every supported Vite version. With Vite 8, [Rolldown](https://rolldown.rs/) emits JavaScript output chunks. The plugin generates a `no_bundle` configuration that uploads those chunks as separate runtime modules without rebuilding them. The generated `no_bundle` setting applies only after Vite processes the source. Vite handles transpilation and package resolution before Wrangler uploads the output. diff --git a/src/content/docs/workers/wrangler/configuration.mdx b/src/content/docs/workers/wrangler/configuration.mdx index 61e1f607ccc..4f450e17200 100644 --- a/src/content/docs/workers/wrangler/configuration.mdx +++ b/src/content/docs/workers/wrangler/configuration.mdx @@ -1760,7 +1760,7 @@ For example, when using the [Cloudflare Vite plugin](/workers/vite-plugin/), an ::: -Vite transforms source modules into JavaScript output chunks before generating this configuration. With Vite 8, Rolldown emits these chunks, and the plugin configures Wrangler to upload them as separate runtime modules with `no_bundle` enabled. +Vite transforms source modules into JavaScript output chunks before generating this configuration. With Vite 8, [Rolldown](https://rolldown.rs/) emits these chunks, and the plugin configures Wrangler to upload them as separate runtime modules with `no_bundle` enabled. The generated setting prevents Wrangler from rebuilding Vite's output. It does not skip Vite's transpilation, npm package resolution, or configured polyfills. From d0e4a1e47649c4f7c0d7cf7256ddba0f921c663d Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sat, 15 Aug 2026 17:59:08 -0700 Subject: [PATCH 4/4] [Workers] Clarify bundled asset path behavior --- .../docs/workers/reference/module-registry.mdx | 16 ++++++++++++++++ .../runtime-apis/webassembly/javascript.mdx | 14 ++++++++++++++ .../reference/migrating-from-wrangler-dev.mdx | 10 ++++++++-- .../vite-plugin/reference/static-assets.mdx | 14 +++++++++++--- .../reference/vite-environments.mdx | 14 +++++++++++++- src/content/docs/workers/wrangler/bundling.mdx | 18 +++++++++++++++--- 6 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/content/docs/workers/reference/module-registry.mdx b/src/content/docs/workers/reference/module-registry.mdx index ae385addd35..4bf02fdaa4d 100644 --- a/src/content/docs/workers/reference/module-registry.mdx +++ b/src/content/docs/workers/reference/module-registry.mdx @@ -88,6 +88,22 @@ export default { `import.meta.filename` and `import.meta.dirname` only exist for `file:` URLs. They are absent from modules with other URL schemes. +### Distinguish runtime module formats + +`__dirname` and `__filename` are CommonJS module-scope values. Workers supplies them only to deployed runtime modules that remain CommonJS. + +ES modules under the new registry use `import.meta.dirname` and `import.meta.filename`. Neither `nodejs_compat` nor `new_module_registry` defines the CommonJS values in ES modules. + +These flags also do not restore CommonJS boundaries removed during bundling. + +A build can emit CommonJS source as an ES module output chunk. The runtime uses the deployed chunk format, not the source format. + +The metadata identifies the current deployed runtime module. After bundling, that is the output chunk rather than the original source file or npm package directory. + +Replacing `__dirname` with `import.meta.dirname` only changes variable access. It does not copy, preserve, or relocate adjacent Wasm, template, or asset files. + +Import required files explicitly or configure your build to include them. + `import.meta.resolve()` only transforms the specifier into a URL. It does not check whether the registry contains that URL. Resolution follows `new URL(specifier, import.meta.url)`. It collapses dot segments but preserves existing percent-encoded bytes. diff --git a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx index 9f1aedfb787..b125d93f499 100644 --- a/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx +++ b/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx @@ -51,6 +51,20 @@ wat2wasm src/simple.wat -o src/simple.wasm [Wrangler](/workers/wrangler/) bundles modules ending in `.wasm` or `.wasm?module`. You can change this default with [module rules](/workers/wrangler/configuration/#bundling). +Statically import each `.wasm` file used through Wrangler's module import API. For files loaded dynamically by package-specific code, configure the relevant build plugin or use `find_additional_modules` and module rules as appropriate. + +Replacing `__dirname` with `import.meta.dirname` only changes variable access. + +It does not copy, preserve, or relocate a binary beside the output chunk. + +Bundlers can emit CommonJS package source inside an ES module chunk. [`nodejs_compat`](/workers/runtime-apis/nodejs/) does not restore `__dirname` in that output. + +If a package expects an adjacent binary, prefer a browser, Worker, or bundler entrypoint that accepts an explicitly imported `WebAssembly.Module`, `ArrayBuffer`, or byte array. A loader that still constructs a path from `__dirname` does not solve this issue. + +You can also use a package initialization hook that accepts the imported module or data. Hook APIs differ between packages. + +Workers does not provide a generic loader hook signature. + For more information, refer to [Wrangler bundling](/workers/wrangler/bundling/). ## Use Wasm from JavaScript diff --git a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx index 465646fb08f..c05dcb0842c 100644 --- a/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx +++ b/src/content/docs/workers/vite-plugin/reference/migrating-from-wrangler-dev.mdx @@ -18,7 +18,13 @@ Vite transforms your source modules into JavaScript output chunks. With Vite 8, The output configuration references the entry chunk and sets `no_bundle` to `true`. It also includes rules that let Wrangler upload the other JavaScript chunks as separate runtime modules. -This generated setting prevents Wrangler from rebuilding Vite's output. Vite already handles transpilation and npm package resolution before deployment. +This generated setting prevents a second Wrangler build. It does not preserve the original module types or boundaries. + +Vite already handles transpilation and npm package resolution before deployment. It can emit CommonJS source in an ES module output chunk. + +Wrangler then uploads that output as an ES module runtime module. [`nodejs_compat`](/workers/runtime-apis/nodejs/) does not define `__dirname` or `__filename` in that module. + +With the new registry, use [`import.meta.dirname` and `import.meta.filename`](/workers/reference/module-registry/#read-module-metadata) for deployed module metadata. The plugin also applies configured Node.js compatibility polyfills. @@ -50,7 +56,7 @@ The following build-related options are handled by Vite and are not applicable w - `base_dir` - `preserve_file_names` -The input `no_bundle` option is ignored because the plugin always builds source with Vite. The generated output configuration uses `no_bundle` only to stop Wrangler from processing the resulting chunks again. +The input `no_bundle` option is ignored because the plugin always builds source with Vite. The generated output configuration uses `no_bundle` to stop Wrangler from transpiling or bundling Vite's output again. Wrangler still discovers and uploads the resulting modules according to the generated rules. This does not preserve source module boundaries. ### Not supported diff --git a/src/content/docs/workers/vite-plugin/reference/static-assets.mdx b/src/content/docs/workers/vite-plugin/reference/static-assets.mdx index bee7c641303..a52966ec287 100644 --- a/src/content/docs/workers/vite-plugin/reference/static-assets.mdx +++ b/src/content/docs/workers/vite-plugin/reference/static-assets.mdx @@ -8,7 +8,7 @@ products: - workers --- -import { WranglerConfig } from "~/components"; +import { TypeScriptExample, WranglerConfig } from "~/components"; This guide focuses on the areas of working with static assets that are unique to the Vite plugin. For more general documentation, see [Static Assets](/workers/static-assets/). @@ -46,12 +46,18 @@ The following example configures the `not_found_handling` for a single-page appl The Vite plugin ensures that all of Vite's [static asset handling](https://vite.dev/guide/assets) features are supported in your Worker as well as in your frontend. These include importing assets as URLs, importing as strings and importing from the `public` directory as well as inlining assets. -Assets [imported as URLs](https://vite.dev/guide/assets#importing-asset-as-url) can be fetched via the [assets binding](/workers/static-assets/binding/#binding). +Workers Assets served through `env.ASSETS` are stored separately from deployed JavaScript modules. They are not `/bundle`-adjacent files available through `node:fs`. + +Use Vite asset imports and the `env.ASSETS` binding instead. Do not build asset paths from `__dirname` or `import.meta.dirname`. + +Assets [imported as URLs](https://vite.dev/guide/assets#importing-asset-as-url) and emitted as files can be fetched through the [assets binding](/workers/static-assets/binding/#binding). Raw and inline imports are embedded in the Worker and do not use the assets binding. As the binding's `fetch` method requires a full URL, we recommend using the request URL as the `base`. This is demonstrated in the following example: + + ```ts -import myImage from "./my-image.png"; +import myImage from "./my-image.png?no-inline"; export default { fetch(request, env) { @@ -60,6 +66,8 @@ export default { }; ``` + + Assets imported as URLs in your Worker will automatically be moved to the client build output. When running `vite build` the paths of any moved assets will be displayed in the console. diff --git a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx index 1ca40114381..e1ef1a3dd8e 100644 --- a/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx +++ b/src/content/docs/workers/vite-plugin/reference/vite-environments.mdx @@ -101,7 +101,19 @@ export default defineConfig({ This environment-wide override changes how Rolldown resolves and transforms modules in the selected Worker environment. It does not add Node.js APIs to the Workers runtime. -Use [`nodejs_compat`](/workers/runtime-apis/nodejs/) for supported Node.js APIs and polyfills. The override does not guarantee that CommonJS modules remain separate during bundling. It also does not guarantee package compatibility. +Setting `platform` to `"node"` does not solve [Rolldown issue 9719](https://github.com/rolldown/rolldown/issues/9719). CommonJS `__dirname` references can remain in ES module output. + +Use [`nodejs_compat`](/workers/runtime-apis/nodejs/) for supported Node.js APIs and polyfills. Neither setting defines `__dirname` or `__filename` in ES modules. + +They also do not restore CommonJS boundaries removed during bundling. + +ES module output can use [`import.meta.dirname` and `import.meta.filename`](/workers/reference/module-registry/#read-module-metadata) with the new module registry. These values identify the deployed output chunk, not the original package directory. + +A `define` replacement such as `__dirname: "import.meta.dirname"` can avoid the missing-variable error. However, it replaces every free `__dirname` in that environment with the deployed chunk directory. It does not reproduce each bundled source module's original directory or emit adjacent files. + +Use this replacement only after verifying the affected packages and deployed asset paths. Prefer package APIs or build plugins that accept an imported Wasm module or asset URL. + +An explicit import only helps when its emitted URL or module value is passed to the package. It does not change package code that still constructs a path from `__dirname`. ## Full-stack frameworks diff --git a/src/content/docs/workers/wrangler/bundling.mdx b/src/content/docs/workers/wrangler/bundling.mdx index 4a6bc804f91..bff70471ab5 100644 --- a/src/content/docs/workers/wrangler/bundling.mdx +++ b/src/content/docs/workers/wrangler/bundling.mdx @@ -41,6 +41,18 @@ Wrangler usually converts many source modules into one entry output chunk. Wrang You can also upload external modules beside the entry module. Wrangler does not inline those modules into the entry output. +A build can emit CommonJS source as an ES module output chunk. Runtime behavior follows the deployed output, not the source format. + +Bundling can remove CommonJS module boundaries. Neither `nodejs_compat` nor `new_module_registry` restores those boundaries or defines `__dirname` and `__filename` in ES module output. Those values exist only in deployed runtime modules that remain CommonJS. + +With the new registry, ES modules use [`import.meta.dirname` and `import.meta.filename`](/workers/reference/module-registry/#read-module-metadata). These values identify the current deployed runtime module. After bundling, that is the output chunk rather than the original source or npm package directory. + +Replacing `__dirname` with `import.meta.dirname` does not include adjacent files. Import required Wasm, template, or asset files explicitly. + +Module rules determine which files Wrangler uploads and their runtime module type. Wrangler hashes statically imported additional-module filenames by default. Set `preserve_file_names` when runtime code requires stable names. + +For files that are not statically imported, configure `find_additional_modules`, `base_dir`, and matching rules. Discovered module names remain relative to `base_dir`. Ensure those deployed paths match the paths used by runtime code. + ## Including non-JavaScript modules {/* Changes to this section should also be applied to /workers/vite-plugin/reference/non-javascript-modules */} @@ -152,10 +164,10 @@ Use [Custom Builds](/workers/wrangler/custom-builds/) to customize what Wrangler Some framework tools or custom build processes generate a modified Wrangler configuration. Wrangler can use this generated configuration instead of your original configuration. -The Cloudflare Vite plugin uses this workflow for every supported Vite version. With Vite 8, [Rolldown](https://rolldown.rs/) emits JavaScript output chunks. The plugin generates a `no_bundle` configuration that uploads those chunks as separate runtime modules without rebuilding them. +The Cloudflare Vite plugin uses this workflow for every supported Vite version. With Vite 8, [Rolldown](https://rolldown.rs/) emits JavaScript output chunks. The plugin generates a `no_bundle` configuration that uploads those chunks as separate runtime modules without a second Wrangler build. -The generated `no_bundle` setting applies only after Vite processes the source. Vite handles transpilation and package resolution before Wrangler uploads the output. +The generated `no_bundle` setting applies only after Vite processes the source. It does not preserve the original module types or boundaries. CommonJS source may already be part of an ES module output chunk. -The plugin also applies configured Node.js compatibility polyfills during the build. +Vite handles transpilation and package resolution before Wrangler uploads the output. The plugin also applies configured Node.js compatibility polyfills during the build. For more information, refer to [Generated Wrangler configuration](/workers/wrangler/configuration/#generated-wrangler-configuration).