Skip to content

perf: bypass wrappers for classless native components - #640

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

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

Conversation

@dlebedynskyi

@dlebedynskyi dlebedynskyi 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.

  <View style={styles.container} />
  // Compiled to raw React Native View

  <View className="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

coderabbitai Bot 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.

dlebedynskyi pushed a commit to dlebedynskyi/uniwind that referenced this pull request Aug 25, 2026
dlebedynskyi pushed a commit to dlebedynskyi/uniwind that referenced this pull request Sep 10, 2026
Sign up for free to 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