Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 13 additions & 2 deletions docs/bundler/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1415,11 +1415,22 @@ In each case the member access compiles to a direct reference to `object`, the `

Assignments (`z.x = 1`), optional chains, and non-literal computed keys (`z[key]`) are left as property accesses; `z["object"]` is treated like `z.object`. `export default someImport` is followed only when it ends at a namespace; a default that snapshots a `let` export keeps snapshot semantics.

The same applies to the default import of a CommonJS module whose `exports.x = ...` assignments the bundler lifted to ES module exports, such as `react` and `scheduler`. The default import of a CommonJS module is its `module.exports`, which is that module's namespace, so `import React from "react"; React.useState()` compiles to a direct call of the lifted `useState` binding. The namespace object is created only when `React` itself is used as a value, and it lists the exports in assignment order, like `module.exports` does. A write through it, `React.useLayoutEffect = React.useEffect`, assigns the lifted binding, so every importer sees the new value, the same as a write to `module.exports`. `React.default`, and `ns.default` on `import * as ns`, is the lifted `default` export when the module has one, and otherwise the namespace itself, as `module.exports` is in Node. A module that sets both `exports.__esModule` and `exports.default` keeps its CommonJS wrapper when the importer is not an ES module by type (`.mjs`, `.mts`, or `"type": "module"`), because the default import then depends on that flag at run time. An `import()` of a module that does not set both resolves to that same namespace object as `default`, with or without code splitting.
The same applies to the default import of a CommonJS module whose `exports.x = ...` assignments the bundler lifted to ES module exports, such as `react` and `scheduler`. The default import of a CommonJS module is its `module.exports` object, so `import React from "react"; React.useState()` compiles to a direct call of the lifted `useState` binding. The `module.exports` object is created only when `React` itself is used as a value, and it lists the exports in assignment order. A write through it, `React.useLayoutEffect = React.useEffect`, assigns the lifted binding, so every importer sees the new value, the same as a write to `module.exports`. `React.default` is the lifted `default` export when the module has one, and otherwise `undefined`. `import * as ns` gives a separate namespace object, as in Node: `ns.default` is that `module.exports` object (the value a default import binds), followed by the named exports, and `ns.useState` still compiles to the lifted binding. A module that sets both `exports.__esModule` and `exports.default` keeps its CommonJS wrapper when the importer is not an ES module by type (that is, not a `.mjs` or `.mts` file and not in a `"type": "module"` package), because the default import and `ns.default` then depend on that flag at run time. An `import()` resolves to that same `module.exports` object as `default`, with or without code splitting, except that an importer that is not an ES module by type gets `exports.default` from a module that sets both. A lifted CommonJS file that is itself an entry point of the build is the other exception: its output file exports its own `exports.default` as `default`, and so does an `import()` of it.

In short, for a lifted CommonJS module `dep`:

| How `dep` is reached | What you get |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `import d from "./dep"`, `import { default as d }`, `export { default } from` | `module.exports` |
| `import * as ns` / `export * as ns from` | a namespace object: `ns.default` is `module.exports`, then the named exports; `ns.x` binds the lifted `x` |
| `import("./dep")`, same chunk or split chunk | that namespace shape: `.default` is `module.exports` |
| `require("./dep")` | `module.exports` |
| any of the above from an importer that is not an ES module by type (not `.mjs`, `.mts`, or `"type": "module"`), when `dep` assigns both `exports.__esModule` and `exports.default` | `default` is `exports.default`, as `bun run` gives it: static imports keep the CommonJS wrapper, a split `import()` unwraps it |
| `dep` is itself an entry point of the build | its output file, and an `import()` of it, export `exports.default` as `default` (#12463) |

### deprecatedNamespaceObjectSetters

Default `true`. When a namespace object does have to be created, each property currently gets a getter and a setter; the setter accepts `ns.foo = value` without throwing (reads still return the module's binding). Set this to `false` to emit getter-only namespace objects, which is what a future Bun release will do unconditionally. The namespace of a lifted CommonJS module is not affected: it stands in for `module.exports`, so its setters assign the lifted bindings either way.
Default `true`. When a namespace object does have to be created, each property currently gets a getter and a setter; the setter accepts `ns.foo = value` without throwing (reads still return the module's binding). Set this to `false` to emit getter-only namespace objects, which is what a future Bun release will do unconditionally. The `module.exports` object of a lifted CommonJS module is not affected: its setters assign the lifted bindings either way.

<Tabs>
<Tab title="JavaScript">
Expand Down
Loading
Loading