Skip to content

@react-native/babel-preset applies transform-destructuring unconditionally under Hermes — semantic divergence breaks three.js TSL (crashes every InstancedMesh on WebGPU) #57934

Description

@smm-h

Description

@react-native/babel-preset pushes @babel/plugin-transform-destructuring unconditionally — including for Hermes targets (packages/react-native-babel-preset/src/configs/main.js, the always-on plugin list; verified in 0.81.4 at src/configs/main.js:122). Hermes supports array/object destructuring natively, so for Hermes builds the transform is (a) redundant bytecode and (b) a semantic hazard, because downleveled destructuring is not perfectly equivalent to native destructuring.

The concrete divergence: native array destructuring always goes through the iterator protocol, while Babel's _slicedToArray helper short-circuits via _arrayWithHolesif (Array.isArray(r)) return r; — and then indexes positionally. For a Proxy over an array (Array.isArray pierces to the target) whose get trap serves a custom Symbol.iterator but does not answer positional reads, the two compile outputs produce different values: iterator yields the real elements, indexing yields undefined.

That exotic-sounding object is shipping in one of the most popular 3D libraries: three.js's TSL shader system passes Fn arguments through exactly such a Proxy, and three's own sources destructure it (src/nodes/accessors/Instance.js: Fn( ( [ instancedMesh ] ) => { const { instanceMatrix, instanceColor } = instancedMesh; ... } )). Metro rewrites three's code with this transform, so on device the body receives undefined and every React Native app using three.js WebGPU (react-native-webgpu, Dawn/Vulkan) with an InstancedMesh crashes on the first rendered frame:

TypeError: Cannot read property 'instanceMatrix' of undefined
  at ... setupOutput / getOutputNode / build / flowNodeFromShaderStage / ... / render

The same pattern arms skinning(), batch() and morphReference() — i.e. skinned meshes, batched meshes and morph targets are equally affected. Nothing surfaces in Node/browser tests because those run untransformed code; the failure exists only in the Metro-bundled app.

Ask

Consider making transform-destructuring conditional on the engine target (skipped for Hermes, as several other transforms already are engine-aware), or documenting why it must stay unconditional. Cross-filed with the two neighboring projects, since each owns a slice of the collision:

Steps to reproduce

  1. Bare RN 0.81.4 app (Hermes, new arch) + react-native-webgpu 0.8.2 + three 0.185.1
  2. Render any scene containing a THREE.InstancedMesh with WebGPURenderer
  3. First frame throws TypeError: Cannot read property 'instanceMatrix' of undefined

A toolchain-only demonstration (no device needed): run @react-native/babel-preset over const fn = ([a]) => a; and observe the _slicedToArray output; feed it an array-backed Proxy with a custom Symbol.iterator in its get trap and no index handling — native semantics return the element, transformed semantics return undefined.

React Native Version

0.81.4

Affected Platforms

Runtime: Android (verified on device: Android 15, Hermes release bundle, Dawn/Vulkan). iOS untested but the transform is platform-independent.

Output of npx @react-native-community/cli info

Not attached — the report is toolchain-level (@react-native/babel-preset 0.81.4, @babel/plugin-transform-destructuring 7.29.7, @babel/runtime 7.29.7); happy to add on request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Author FeedbackNeeds: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions