Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2432,7 +2432,7 @@ pub mod __gated_printer {
let record = self.import_record(import_record_index as usize);
let module_type = self.options.module_type;

if IS_BUN_PLATFORM {
if IS_BUN_PLATFORM && self.options.bundling {
Comment thread
robobun marked this conversation as resolved.
// "bun" is not a real module. It's just globalThis.Bun.
//
// transform from:
Expand All @@ -2443,6 +2443,9 @@ pub mod __gated_printer {
// const foo = await Promise.resolve(globalThis.Bun)
// const bar = globalThis.Bun
//
// Gated on `bundling`: the runtime transpiler uses NoOpRenamer, so a
// user `let globalThis` would capture the literal (#8058). Fall through
// to the external require/import paths there instead.
Comment thread
robobun marked this conversation as resolved.
if record.tag == ImportRecordTag::Bun {
if record.kind == ImportKind::Dynamic {
self.print(b"Promise.resolve(globalThis.Bun)");
Expand Down Expand Up @@ -5762,6 +5765,9 @@ pub mod __gated_printer {
self.add_source_mapping(stmt.loc);

if IS_BUN_PLATFORM {
// Not gated on `bundling` (cf. print_require_or_import_expr): a real
// ESM `import {x} from "bun"` reifies every Bun property and rejects
// type-only names at link time.
Comment thread
robobun marked this conversation as resolved.
Outdated
if record.tag == ImportRecordTag::Bun {
self.print_global_bun_import_statement(s);
self.prev_stmt_tag = new_tag;
Expand Down
3 changes: 2 additions & 1 deletion src/js_printer/renamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,8 @@ pub fn compute_initial_reserved_names(

let mut names = StringHashMap::<u32>::default();

const EXTRAS: [&[u8]; 2] = [b"Promise", b"Require"];
// Identifiers the printer may emit as raw text with no Ref (#8058).
const EXTRAS: [&[u8]; 3] = [b"Promise", b"Require", b"globalThis"];

const CJS_NAMES: [&[u8]; 2] = [b"exports", b"module"];

Expand Down
4 changes: 3 additions & 1 deletion src/jsc/RuntimeTranspilerCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ bun_core::declare_scope!(cache, visible);
/// path reinstates the bug for any previously-cached TLA module (#30887).
/// Version 23: `jsx.runtime`/`jsx.development` participate in the features hash,
/// and tsconfig `"jsx": "react-jsx"` now emits the production runtime (#4227).
const EXPECTED_VERSION: u32 = 23;
/// Version 24: `require("bun")` / `import("bun")` are no longer rewritten to the
/// literal `globalThis.Bun` in the runtime transpile path (#8058).
Comment thread
robobun marked this conversation as resolved.
Outdated
const EXPECTED_VERSION: u32 = 24;

/// Source files smaller than this are not written to / read from the on-disk
/// transpiler cache. Originally 50 KiB, which excluded almost every file in a
Expand Down
6 changes: 5 additions & 1 deletion test/js/bun/resolve/import-meta.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ it('require("bun") works', () => {
});

it('import("bun") works', async () => {
expect(await import("bun")).toBe(Bun);
const ns = await import("bun");
expect(ns.default).toBe(Bun);
expect(ns.serve).toBe(Bun.serve);
// Consistent with a non-constant specifier (see "dynamically import bun" below)
expect(ns).toBe(await import(eval("'bun'")));
});

it("require.resolve with empty options object", () => {
Expand Down
132 changes: 132 additions & 0 deletions test/regression/issue/08058.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// https://github.com/oven-sh/bun/issues/8058
//
// The printer used to unconditionally rewrite `require("bun")` / `import("bun")`
// to the literal `globalThis.Bun` / `Promise.resolve(globalThis.Bun)`. A local
// `let globalThis` (or `let Promise`) in scope would shadow that literal and
// change the value of the import.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { itBundled } from "../../bundler/expectBundled";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

describe("runtime transpiler", () => {
async function run(src: string) {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", src],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { stdout, stderr, exitCode };
}

test("require('bun') with a local `globalThis` returns the real Bun object", async () => {
const { stdout, stderr, exitCode } = await run(
`{ let globalThis = { Bun: "intercepted" }; console.log(typeof require("bun").serve); }`,
);
expect(stderr).toBe("");
expect(stdout).toBe("function\n");
expect(exitCode).toBe(0);
});

test("dynamic import('bun') with local `globalThis` / `Promise` returns the real Bun object", async () => {
const { stdout, stderr, exitCode } = await run(
`{ let globalThis = { Bun: "intercepted" }; let Promise = null;` +
` import("bun").then(b => console.log(typeof b.serve)); }`,
);
expect(stderr).toBe("");
expect(stdout).toBe("function\n");
expect(exitCode).toBe(0);
});

test("require('bun') in a .cjs file with a local `globalThis`", async () => {
using dir = tempDir("issue-08058-cjs", {
"index.cjs": `
module.exports = 1;
{ let globalThis = { Bun: "intercepted" }; console.log(typeof require("bun").serve); }
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.cjs"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout).toBe("function\n");
expect(exitCode).toBe(0);
});

test("`import { env } from 'bun'` does not eagerly reify the Bun object", async () => {
// `import X from "bun"` is still lowered to `var X = globalThis.Bun` at runtime
// (not a real ESM import) so that (a) only the requested property is touched and
// (b) type-only names declared in bun.d.ts don't fail link-time export validation.
using dir = tempDir("issue-08058-reify", {
"index.ts": `
import { env } from "bun";
import { hasNonReifiedStatic } from "bun:internal-for-testing";
if (!hasNonReifiedStatic(Bun)) throw new Error("import { env } from 'bun' reified the whole Bun object");
void env;
console.log("pass");
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.ts"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout).toBe("pass\n");
expect(exitCode).toBe(0);
});
});

describe("bundler", () => {
itBundled("bun/require-bun-shadowed-globalThis", {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
target: "bun",
files: {
"/entry.ts": /* js */ `
import * as B from "bun";
var globalThis = { Bun: "intercepted" };
if (typeof B.serve !== "function") throw new Error("import * from 'bun' was shadowed: " + B);
{
let globalThis = { Bun: "intercepted" };
const b = require("bun");
if (typeof b.serve !== "function") throw new Error("require('bun') was shadowed: " + b);
const d = await import("bun");
if (typeof d.serve !== "function") throw new Error("import('bun') was shadowed: " + d);
console.log("pass");
}
`,
},
run: { stdout: "pass" },
onAfterBundle(api) {
// The user's local binding should have been renamed away.
expect(api.readFile("out.js")).not.toContain(`globalThis = { Bun`);
},
});

itBundled("bun/require-bun-shadowed-globalThis-cjs", {
target: "bun",
format: "cjs",
files: {
"/entry.ts": /* js */ `
import * as B from "bun";
{
let globalThis = { Bun: "intercepted" };
const b = require("bun");
if (typeof b.serve !== "function") throw new Error("require('bun') was shadowed: " + b);
if (typeof B.serve !== "function") throw new Error("import * from 'bun' was shadowed: " + B);
console.log("pass");
}
`,
},
run: { stdout: "pass" },
});
});
Loading