Summary
Using latest npm Perry (@perryts/perry@0.5.1025) with React 19's reconciler, basic package import/root creation works, but the first real Fiber render hangs.
The smallest failing surface I found is react-reconciler HostRoot update processing:
const root = Renderer.createContainer(container, LegacyRoot, null, false, null, "id", onError, onError, onError, null);
Renderer.updateContainerSync(null, root, null, () => {});
Renderer.flushSyncWork(); // hangs under Perry
This reproduces even with null, so the failure is before host component creation, text nodes, function components, or mutation methods.
Versions
@perryts/perry@0.5.1025
react@19.2.6
react-reconciler@0.33.0
scheduler@0.27.0
The repro was bundled with esbuild to avoid CJS package loading noise.
Evidence
Instrumenting the bundled react-reconciler.production.js shows Perry reaches HostRoot and then throws inside processUpdateQueue:
trace: updateContainerSync.before
trace: updateContainerSync.after
trace: flushSyncWork.before
internal.performUnitOfWork.enter tag=3
internal.pushHostContainer.afterContextPush
internal.hostRoot.beforeCloneUpdateQueue
internal.hostRoot.afterCloneUpdateQueue
internal.processUpdateQueue.enter
internal.processUpdateQueue.loop tag=0 lane=2
internal.processUpdateQueue.case0 beforePayload payloadType=object
internal.processUpdateQueue.case0 beforeAssign assignType=number newStateType=object updateLaneType=object
internal.renderRootSync.catch value=[object Object] type=object then=undefined
internal.renderRootSync.catch value=[object Object] type=object then=undefined
...
The React reconciler code at that point is:
var assign = Object.assign;
// inside processUpdateQueue(...)
newState = assign({}, newState, update.payload);
Under Perry, that call throws, React catches the thrown object in renderRootSync, retries, and loops forever. If I patch just that call to direct Object.assign({}, newState, update.payload), Perry advances past HostRoot update processing and reaches renderer host calls like getChildHostContext, shouldSetTextContent, and createInstance.
Expected
Captured stdlib function aliases like this should remain callable and equivalent to the member call:
var assign = Object.assign;
assign({}, a, b);
React packages commonly capture stdlib members this way.
Guidance
Please investigate Perry's lowering/runtime representation for function-valued stdlib member references captured into locals, especially inside bundled CommonJS wrapper scopes. The failing bundle has multiple local assign = Object.assign bindings from React and react-reconciler; at the failing call site Perry reports the local alias as number and the call throws.
It would also help if thrown non-Error objects preserved enough diagnostic information to avoid this kind of silent retry loop. React's sync work loop keeps retrying because the thrown value is opaque ([object Object]) and not a thenable.
Summary
Using latest npm Perry (
@perryts/perry@0.5.1025) with React 19's reconciler, basic package import/root creation works, but the first real Fiber render hangs.The smallest failing surface I found is
react-reconcilerHostRoot update processing:This reproduces even with
null, so the failure is before host component creation, text nodes, function components, or mutation methods.Versions
The repro was bundled with esbuild to avoid CJS package loading noise.
Evidence
Instrumenting the bundled
react-reconciler.production.jsshows Perry reaches HostRoot and then throws insideprocessUpdateQueue:The React reconciler code at that point is:
Under Perry, that call throws, React catches the thrown object in
renderRootSync, retries, and loops forever. If I patch just that call to directObject.assign({}, newState, update.payload), Perry advances past HostRoot update processing and reaches renderer host calls likegetChildHostContext,shouldSetTextContent, andcreateInstance.Expected
Captured stdlib function aliases like this should remain callable and equivalent to the member call:
React packages commonly capture stdlib members this way.
Guidance
Please investigate Perry's lowering/runtime representation for function-valued stdlib member references captured into locals, especially inside bundled CommonJS wrapper scopes. The failing bundle has multiple local
assign = Object.assignbindings from React andreact-reconciler; at the failing call site Perry reports the local alias asnumberand the call throws.It would also help if thrown non-
Errorobjects preserved enough diagnostic information to avoid this kind of silent retry loop. React's sync work loop keeps retrying because the thrown value is opaque ([object Object]) and not a thenable.