Skip to content

guardCreateRequire / stripBareNodeImports rewrite matches inside string literals #4526

Description

@zsilbi

Environment

  • nitro pkg.pr.new/nitro@078fa53 (main snapshot, 2026-08)
  • Reproduced with the cloudflare_module preset, checked with wrangler dev

Reproduction

Any chunk whose string data contains createRequire(import.meta.url). Distilled to a minimal server
handler (our real case embedded a copy of h3/dist/h3.mjs as data for a dynamic-worker loader):

// server/routes/kit.ts
export default defineEventHandler(() => {
  // Module text carried as data — e.g. sources for a sandbox/loader, embedded at build time.
  const kit = {
    "h3.mjs": "const _require = createRequire(import.meta.url);\nexport default _require;",
  };

  return Object.keys(kit);
});

Build with the cloudflare_module preset, then wrangler dev .output/server/index.mjs:

✘ [ERROR] Expected "}" but found "file"

    .output/server/index.mjs:101:119284

Describe the bug

guardCreateRequire runs in generateBundle (from dist/_presets3.mjs):

function guardCreateRequire() {
	return {
		name: "nitro:cloudflare-guard-createRequire",
		generateBundle(_options, bundle) {
			for (const chunk of Object.values(bundle)) if (chunk.type === "chunk" && chunk.code?.includes("createRequire(import.meta.url)")) chunk.code = chunk.code.replace(/createRequire\(import\.meta\.url\)/g, "createRequire(import.meta.url || \"file:///\")");
		}
	};
}

The replace is applied to the whole chunk text, so the occurrence inside the string literal is rewritten
too — and the replacement's " characters are not escaped for the literal they land in:

// before (valid):
const kit = { "h3.mjs": "const _require = createRequire(import.meta.url);\n..." };

// after (parse error — the inserted `"` terminates the literal):
const kit = { "h3.mjs": "const _require = createRequire(import.meta.url || "file:///");\n..." };

stripBareNodeImports has the same defect class. Its multiline regex

const BARE_NODE_IMPORT_RE = /^import\s*['"]node:[^'"]+['"];?\s*$/gm;

matches a line inside a template literal carrying real newlines, silently deleting content from the
embedded text:

// before:
const source = `import "node:fs";
export const marker = 1;`;

// after (the first line of the DATA is gone):
const source = `export const marker = 1;`;

The rewrite itself is correct and needed for real code (workerd defines no import.meta.url). It must
just be scoped to code: matches inside string literals, template text, and comments are data and must
survive byte-identical.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions