Fix import.meta handling in CJS format

In CommonJS format, import.meta and import.meta.url need to be
transformed into valid CJS equivalents:

- import.meta -> {url: require('url').pathToFileURL(__filename).href}
- import.meta.url -> require('url').pathToFileURL(__filename).href

This change updates js_printer.zig to handle these transformations
when module_type is .cjs. The ESM format remains unchanged.

Added comprehensive tests to verify the transformation works correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-10-14 22:40:35 +00:00
parent bd88717ddc
commit 86686c468c
2 changed files with 149 additions and 4 deletions

View File

@@ -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;

View File

@@ -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 });
}
});
});