Skip to content

require(esm): __esModule is not an own enumerable property (Object.keys omits it) #40340

Open

Description

@xia-chao

What version of Bun is running?

text 1.4.1-canary.1+f2fe7d324

What platform is your computer?

text Darwin 24.6.0 arm64 arm

What steps can reproduce the bug?

Node.js documents that require() of an ES module with export default returns a namespace that includes an own enumerable __esModule: true, so CJS tooling that does Object.keys / Object.assign / object spread can see the interop marker.

On Bun, "__esModule" in mod and mod.__esModule === true both succeed, but Object.keys(mod) omits "__esModule". That matches a prototype getter / non-enumerable marker, not Node’s own enumerable property.

Save as repro.mjs and run node repro.mjs then bun repro.mjs:

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";

const dir = fs.mkdtempSync(path.join(os.tmpdir(), "require-esm-"));
const file = path.join(dir, "mod.mjs");
fs.writeFileSync(file, "export default 42;\nexport const x = 1;\n");
try {
  const req = createRequire(path.join(dir, "entry.cjs"));
  const mod = req(file);
  console.log({
    runtime: typeof Bun === "undefined" ? "node" : "bun",
    ownNames: Object.keys(mod),
    hasEsModule: "__esModule" in mod,
    esModule: mod.__esModule,
    default: mod.default,
    x: mod.x,
  });
} finally {
  fs.rmSync(dir, { recursive: true, force: true });
}

What is the expected behavior?

Node v26.7.0:

{
  runtime: 'node',
  ownNames: [ '__esModule', 'default', 'x' ],
  hasEsModule: true,
  esModule: true,
  default: 42,
  x: 1
}

__esModule must be an own enumerable property so Object.keys(mod) includes it.

What do you see instead?

{
  runtime: "bun",
  ownNames: [ "default", "x" ],
  hasEsModule: true,
  esModule: true,
  default: 42,
  x: 1,
}

in / value access work; enumeration does not.

Additional information

This still reproduces on 1.4.1-canary.1+f2fe7d324. Related in-flight work:

Those PRs describe exactly this: the marker must be an own enumerable property on a null-prototype namespace, not a prototype getter. Opening this in case the canary does not yet include that change.

Compared against Node v26.7.0.

Activity

  1. robobun commented on Aug 24, 2026

    @robobun
    Collaborator

    Reproduced on main at 861e9ae: Object.keys(mod) omits __esModule while "__esModule" in mod is true. Fix in progress: #33894 makes the marker an own enumerable property on a null-prototype namespace. It waits on a JSC-side change (oven-sh/WebKit#279).

  2. robobun commented on Nov 7, 2026

    @robobun
    Collaborator

    Status update. #33894 is closed, and oven-sh/WebKit#279 with it. That design put the marker on the one namespace object that import also returns, so the marker leaked to the import side.

    The design that matches Node: require() returns a second namespace object when the module has a default export and no __esModule export. __esModule is a real export of that object, so Object.keys, object spread and Object.assign see it. The import namespace keeps a null prototype and never shows the marker, which also covers #39866 and #21614.

    It waits on two things:

    The closing comment on #33894 has the details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions