diff --git a/src/js_printer.zig b/src/js_printer.zig index f1dcdb9882..5f8ac9c653 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2017,6 +2017,16 @@ fn NewPrinter( bun.assert(p.options.hmr_ref.isValid()); p.printSymbol(p.options.hmr_ref); p.print(".importMeta"); + } else if (p.options.module_type == .cjs) { + // In CJS format, import.meta should be transformed into an object + // with at least the `url` property + if (p.options.import_meta_ref.isValid()) { + // Use the synthetic import.meta ref if available + p.printSymbol(p.options.import_meta_ref); + } else { + // Create import.meta object inline + p.print("{url: require('url').pathToFileURL(__filename).href}"); + } } else if (!p.options.import_meta_ref.isValid()) { // Most of the time, leave it in there p.print("import.meta"); @@ -2026,10 +2036,6 @@ fn NewPrinter( // // This is currently only used in Bun's runtime for CommonJS modules // referencing import.meta - // - // TODO: This assertion trips when using `import.meta` with `--format=cjs` - bun.debugAssert(p.options.module_type == .cjs); - p.printSymbol(p.options.import_meta_ref); } }, @@ -2402,6 +2408,14 @@ fn NewPrinter( p.printInlinedEnum(inlined, e.name, level); return; } + + // Handle import.meta.url in CJS format + if (p.options.module_type == .cjs and e.target.data == .e_import_meta and strings.eqlComptime(e.name, "url")) { + p.printSpaceBeforeIdentifier(); + p.addSourceMapping(expr.loc); + p.print("require('url').pathToFileURL(__filename).href"); + return; + } } else { if (flags.contains(.has_non_optional_chain_parent)) { wrap = true; diff --git a/test/regression/issue/import-meta-cjs-format.test.ts b/test/regression/issue/import-meta-cjs-format.test.ts new file mode 100644 index 0000000000..15ee9f6018 --- /dev/null +++ b/test/regression/issue/import-meta-cjs-format.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, test } from "bun:test"; +import { mkdtempSync, rmSync, writeFileSync } from "fs"; +import { bunEnv, bunExe } from "harness"; +import { tmpdir } from "os"; +import { join } from "path"; + +describe("import.meta in CJS format", () => { + test("import.meta should be transformed to object in CJS format", async () => { + const dir = mkdtempSync(join(tmpdir(), "import-meta-cjs-")); + + try { + const entryFile = join(dir, "entry.js"); + const outFile = join(dir, "out.js"); + + writeFileSync( + entryFile, + `console.log(typeof import.meta); +console.log(import.meta);`, + ); + + const result = Bun.spawnSync({ + cmd: [bunExe(), "build", entryFile, "--format=cjs", `--outfile=${outFile}`], + env: bunEnv, + stderr: "pipe", + }); + + expect(result.exitCode).toBe(0); + + const output = await Bun.file(outFile).text(); + + // import.meta should be transformed into an object + expect(output).toContain("{url: require('url').pathToFileURL(__filename).href}"); + expect(output).not.toContain("import.meta"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + test("import.meta.url should be transformed in CJS format", async () => { + const dir = mkdtempSync(join(tmpdir(), "import-meta-url-cjs-")); + + try { + const entryFile = join(dir, "entry.js"); + const outFile = join(dir, "out.js"); + + writeFileSync(entryFile, `console.log(import.meta.url);`); + + const result = Bun.spawnSync({ + cmd: [bunExe(), "build", entryFile, "--format=cjs", `--outfile=${outFile}`], + env: bunEnv, + stderr: "pipe", + }); + + expect(result.exitCode).toBe(0); + + const output = await Bun.file(outFile).text(); + + // import.meta.url should be transformed + expect(output).toContain("require('url').pathToFileURL(__filename).href"); + expect(output).not.toContain("import.meta.url"); + expect(output).not.toContain("import.meta"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + test("multiple import.meta.url references should be transformed", async () => { + const dir = mkdtempSync(join(tmpdir(), "import-meta-url-multiple-")); + + try { + const entryFile = join(dir, "entry.js"); + const outFile = join(dir, "out.js"); + + writeFileSync( + entryFile, + `const url1 = import.meta.url; +const url2 = import.meta.url; +console.log(url1, url2);`, + ); + + const result = Bun.spawnSync({ + cmd: [bunExe(), "build", entryFile, "--format=cjs", `--outfile=${outFile}`], + env: bunEnv, + stderr: "pipe", + }); + + expect(result.exitCode).toBe(0); + + const output = await Bun.file(outFile).text(); + + // All import.meta.url should be transformed + expect(output).not.toContain("import.meta"); + const matches = output.match(/require\('url'\)\.pathToFileURL\(__filename\)\.href/g); + expect(matches).not.toBeNull(); + expect(matches!.length).toBe(2); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + test("import.meta in ESM format should remain unchanged", async () => { + const dir = mkdtempSync(join(tmpdir(), "import-meta-esm-")); + + try { + const entryFile = join(dir, "entry.js"); + const outFile = join(dir, "out.js"); + + writeFileSync( + entryFile, + `console.log(import.meta); +console.log(import.meta.url);`, + ); + + const result = Bun.spawnSync({ + cmd: [bunExe(), "build", entryFile, "--format=esm", `--outfile=${outFile}`], + env: bunEnv, + stderr: "pipe", + }); + + expect(result.exitCode).toBe(0); + + const output = await Bun.file(outFile).text(); + + // import.meta should remain in ESM format + expect(output).toContain("import.meta"); + expect(output).not.toContain("require('url').pathToFileURL(__filename).href"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +});