Skip to content

perf: bypass wrappers for classless native components - #640

Draft
dlebedynskyi wants to merge 3 commits into
uni-stack:mainfrom
dlebedynskyi:perf/metro-component-dispatch
Draft

perf: bypass wrappers for classless native components#640
dlebedynskyi wants to merge 3 commits into
uni-stack:mainfrom
dlebedynskyi:perf/metro-component-dispatch

Conversation

@dlebedynskyi

@dlebedynskyidlebedynskyi commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Motivation

withUniwindConfig currently redirects supported React Native components to Uniwind wrappers, even when an element has no Uniwind class props.

For large trees, these wrappers add React fibers and hooks to otherwise plain React Native nodes. This overhead becomes significant beyond roughly 1,000 rendered items.

Related discussion: #639

Change

Adds an experimental Metro optimization:

module.exports=withUniwindConfig(config,{cssEntryFile: './global.css',experimental: {optimizeClasslessComponents: true,},})

The option is disabled by default.

When enabled, the Metro transform redirects statically classless elements to raw React Native components before React element creation.

<Viewstyle={styles.container}/>// Compiled to raw React Native View<ViewclassName="flex-1"/>// Retains the Uniwind View wrapper<View{...props}/>// Retains the wrapper because props are unknown

This removes the Uniwind wrapper fiber and hooks from classless elements without adding a runtime dispatcher.

The existing wrapper path remains the conservative fallback for:

  • className and all supported *ClassName props
  • Prop spreads and dynamic props
  • Non-JSX component references
  • Unsupported or unresolved component bindings

No application Babel configuration is required. The transform is installed internally by withUniwindConfig.

Benchmark

Android release build using Hermes and Fabric on an API 35 arm64 emulator. Each result is the median of seven measured mounts after two warm-ups.

Classless StyleSheet tree

 Items Raw React Native Optimization off Optimization on Change
━━━━━━━ ━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━ ━━━━━━━━
100 7.19 ms 13.30 ms 8.78 ms -34.0%
─────── ────────────────── ────────────────── ───────────────── ────────
250 14.73 ms 32.25 ms 21.47 ms -33.4%
─────── ────────────────── ────────────────── ───────────────── ────────
500 37.14 ms 58.63 ms 38.86 ms -33.7%
─────── ────────────────── ────────────────── ───────────────── ────────
1,000 62.93 ms 85.05 ms 69.09 ms -18.8%
─────── ────────────────── ────────────────── ───────────────── ────────
2,000 164.84 ms 207.59 ms 158.75 ms -23.5%

Cross-platform benchmark report

 Platform Items Raw RN Option off Option on Improvement
━━━━━━━━━━ ━━━━━━━ ━━━━━━━━━━━ ━━━━━━━━━━━━ ━━━━━━━━━━━ ━━━━━━━━━━━━━
Android 1,000 62.77 ms 86.56 ms 64.32 ms 25.8%
────────── ─────── ─────────── ──────────── ─────────── ─────────────
Android 2,000 164.51 ms 203.16 ms 163.37 ms 18.4%
────────── ─────── ─────────── ──────────── ─────────── ─────────────
iOS 1,000 79.27 ms 103.28 ms 79.08 ms 21.6%
────────── ─────── ─────────── ──────────── ─────────── ─────────────
iOS 2,000 176.83 ms 220.74 ms 176.45 ms 19.9%

Each platform used 12 balanced rounds and 84 samples per item count and variant. Option-on is statistically indistinguishable from raw RN while significantly outperforming option-off.

Tested on:

  • Android: Android 15 / API 35 arm64 emulator, 2 vCPUs, 2.5 GB guest RAM.

  • iOS: iPhone SE (3rd generation), iOS 18.6 arm64 simulator, Xcode 26.3.

  • Supported syntax

    The transform recognizes:

    • Named React Native imports
    • Namespace and default imports
    • Constant aliases
    • CommonJS destructuring
    • JSX elements
    • Static React.createElement calls

    Validation

    • 279 native tests
    • 26 web tests
    • 133 dispatch assertions covering all 22 supported components and every supported class prop
    • TypeScript and test TypeScript checks
    • Lint and formatting checks
    • Circular dependency check
    • Package build
    • Bare React Native Android release bundle
    • Expo Android production test
    • Expo iOS production test

@coderabbitai

coderabbitaiBot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba52bafb-9925-4bb6-8acb-931a5bc69eaa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@dlebedynskyi