Skip to content

fix(ssr): convert bundled CJS require() of node builtins for webworker SSR (fix #22618)#22619

Open
basuke wants to merge 1 commit into
vitejs:mainfrom
basuke:fix/webworker-cjs-builtin-require
Open

fix(ssr): convert bundled CJS require() of node builtins for webworker SSR (fix #22618)#22619
basuke wants to merge 1 commit into
vitejs:mainfrom
basuke:fix/webworker-cjs-builtin-require

Conversation

@basuke

@basuke basuke commented Jun 5, 2026

Copy link
Copy Markdown

Description

Follow-up to #21963 (fix #21969), which added esmExternalRequirePlugin() for ssr.target: 'webworker' builds so external CJS require() becomes ESM imports (avoiding createRequire(import.meta.url), which throws on workerd).

The plugin was invoked with no external option, so it matched nothing. A bundled CommonJS dependency that internally calls require("<node builtin>") — e.g. node-forge doing require("crypto") — therefore fell through to Rolldown's throwing __require stub:

Calling `require` for "crypto" in an environment that doesn't expose the `require` function.

This breaks on Cloudflare Workers (workerd), and fails the build outright when the SSR output is evaluated during analysis (e.g. SvelteKit adapter-cloudflare). Vite 7 / Rollup converted the same nested require to a static import nodeCrypto from "crypto", so this is a regression for the webworker target.

Fixes #22618.

Change

Pass nodeLikeBuiltins (the existing helper in utils.ts) as the plugin's external, so nested builtin require() in bundled CJS is converted to ESM imports that the runtime (workerd's nodejs_compat) resolves:

plugins.push(esmExternalRequirePlugin({ external: nodeLikeBuiltins }))

Alternatives explored

  • Setting ssr.target: 'webworker' alone — insufficient (this is exactly the gap).
  • ssr.noExternal: true — builds, but crypto resolves to a browser stub missing randomBytes at runtime.
  • Externalizing the offending dep (ssr.external) — the nested require() inside the bundled CJS wrapper still hits the throwing stub.

Tests

Extends the ssr-webworker playground with a bundled CJS fixture that require("node:util"), plus a runtime assertion (executes under miniflare) and a build-output assertion (require("node:util") must become an import, not a __require stub). Verified these fail on main (var util = __require("node:util") + throwing stub) and pass with the change. Full ssr-webworker suite green in both serve and build modes.

…r SSR (fix vitejs#22618)

vitejs#21963 added `esmExternalRequirePlugin()` for `ssr.target: 'webworker'` builds,
but invoked it with no `external`, so the plugin matched nothing. A bundled
CommonJS dependency that internally calls `require("<node builtin>")` (e.g.
node-forge requiring "crypto") therefore fell through to Rolldown's throwing
`__require` stub — breaking on workerd (and failing the build when the SSR
output is evaluated, as in SvelteKit's adapter-cloudflare).

Pass `nodeLikeBuiltins` as the plugin's `external` so those nested builtin
requires are converted to ESM imports the runtime (e.g. workerd's
nodejs_compat) resolves, matching how Vite 7 / Rollup handled them.

Adds a regression fixture (a bundled CJS module requiring `node:util`) to the
ssr-webworker playground, with runtime and build-output assertions.
@basuke basuke force-pushed the fix/webworker-cjs-builtin-require branch from 1266678 to 2e7844f Compare June 5, 2026 15:34
@basuke

basuke commented Jun 5, 2026

Copy link
Copy Markdown
Author

The failing check (Build&Test node-24, macOS only) is an unrelated flaky test — playground/import-assertion/__tests__/import-assertion.spec.ts, failing with the known dev-server race (The server is being restarted or closed. Request is outdated / Failed to run dependency scan. Skipping dependency pre-bundling.). The same test-serve step passes on all Ubuntu Node versions (20/22/24/26) and on Windows, and the tests added in this PR (ssr-webworker) pass on every platform. A re-run of the macOS job should clear it.

// (e.g. workerd's nodejs_compat) can resolve.
if (isSsrTargetWebworkerEnvironment) {
plugins.push(esmExternalRequirePlugin())
plugins.push(esmExternalRequirePlugin({ external: nodeLikeBuiltins }))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems adding this removes the [plugin rolldown:vite-resolve] Automatically externalized node built-in module "node:util" imported from "src\cjs-node-builtin.cjs". Consider adding it to environments.ssr.external if it is intended. warning. I think we need to keep that.

@sapphi-red sapphi-red added feat: ssr p2-edge-case Bug, but has workaround or limited in scope (priority) labels Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat: ssr p2-edge-case Bug, but has workaround or limited in scope (priority)

Projects

None yet

2 participants