Skip to content

[Bug]: react/jsx-runtime CJS facade emitted as separate common chunk instead of merging into entry #9331

@IWANABETHATGUY

Description

@IWANABETHATGUY

Reproduction link or steps

A repository-style minimal reproduction (mirrors the test added in #9329):

.
├── entry.js
├── route.js
├── child.js
└── node_modules/react/
    ├── package.json
    ├── index.js
    ├── jsx-runtime.js
    └── cjs/
        ├── react.production.js
        └── react-jsx-runtime.production.js

entry.js:

import React from 'react';
import { jsx } from 'react/jsx-runtime';

console.log('entry', React.version, jsx('main', {}));
import('./route.js');

route.js:

import React from 'react';
import { jsx } from 'react/jsx-runtime';

console.log('route', React.version, jsx('route', {}));
import('./child.js');

child.js:

import { jsx } from 'react/jsx-runtime';

console.log('child', jsx('child', {}));

node_modules/react/package.json:

{
  "name": "react",
  "main": "./index.js",
  "exports": {
    ".": "./index.js",
    "./jsx-runtime": "./jsx-runtime.js"
  }
}

node_modules/react/index.js:

'use strict'
module.exports = require('./cjs/react.production.js')

node_modules/react/jsx-runtime.js:

'use strict'
module.exports = require('./cjs/react-jsx-runtime.production.js')

node_modules/react/cjs/react.production.js:

'use strict'
exports.version = '19'

node_modules/react/cjs/react-jsx-runtime.production.js:

'use strict'
exports.jsx = (type) => ({ type })
exports.jsxs = exports.jsx
exports.Fragment = Symbol.for('react.fragment')

Build with a single user entry (entry.js).

What is expected?

All consumers of react/jsx-runtime (entry, route, child) are reachable through the same user entry, so its modules should merge into entry.js. Output should be 3 chunks:

- child.js (dynamic entry)
- entry.js (user entry, contains react + jsx-runtime modules)
- route.js (dynamic entry)

What is actually happening?

A separate jsx-runtime.js common chunk is emitted, even though there is no other user entry that could share it:

- child.js                  (dynamic entry)
- entry.js                  (user entry)
- jsx-runtime.js            (common chunk, exports ["n", "r", "t"])  <-- unexpected
- route.js                  (dynamic entry)

Root cause (per #9329): when multiple pending common chunks should all merge into the same target entry, the optimizer's would_create_circular_dependency check treats sibling pending merges as still external, so they appear to form a transient cycle and get split into a standalone common chunk.

System Info

rolldown: 1.0.0-rc.18 (also reproducible on current main @ d3ae8ba2f)
OS: Windows 11

Any additional comments?

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions