Skip to content

Image component boosting proposal#66

Open
adamivancza wants to merge 9 commits into
kuatsu:mainfrom
adamivancza:boost-image
Open

Image component boosting proposal#66
adamivancza wants to merge 9 commits into
kuatsu:mainfrom
adamivancza:boost-image

Conversation

@adamivancza

@adamivancza adamivancza commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Adds a boosted Image optimizer that rewrites safe react-native Image usages to NativeImage.

Includes support for:

  • static and dynamic source / src
  • source={require(...)}
  • dynamic image style handling
  • request headers from crossOrigin / referrerPolicy
  • Image accessibility prop normalization
  • native/pass-through Image host props

Validation

  • Added fixture coverage for static, dynamic, request-header, and require-source cases
  • Added runtime helper tests
  • Added differential parity coverage against React Native Image wrappers
  • Ran typecheck, unit tests, parity tests, and build

Summary by CodeRabbit

  • New Features

    • Added support for optimizing Image elements, including handling of static and dynamic sources, accessibility props, and image-specific styles.
    • Expanded runtime behavior so image components are processed appropriately across supported platforms.
  • Bug Fixes

    • Improved platform-specific handling for image props such as sizing, request headers, and accessibility settings.
    • Added fallback behavior to keep images working when platform-specific image support is unavailable.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Kuatsu Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 360e21a0-a24a-45e9-8ce8-5bffa24438c9

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

Walkthrough

This PR adds a new "Image" optimizer to react-native-boost, transforming eligible <Image> JSX into a NativeImage element with static or runtime prop synthesis for source, style, resizeMode, tintColor, request headers, and accessibility. It includes runtime helpers, type/plugin wiring, extensive fixtures, conformance tests, and parity/fuzz test coverage.

Changes

Image Optimizer Feature

Layer / File(s) Summary
Type contracts and plugin wiring
packages/react-native-boost/src/plugin/types/index.ts, plugin/index.ts, plugin/utils/common/base.ts, plugin/utils/generate-test-plugin.ts
Adds image option, TargetPlatform type, dangerouslyOptimizeImageWithUnknownAncestors, NativeImage host-kind mapping, and wires imageOptimizer into the plugin and test-plugin generators.
Image optimizer Babel transform
plugin/optimizers/image/index.ts
Implements bailout checks, static/runtime source and style building, resizeMode/tintColor synthesis, request header handling, and accessibility prop injection, replacing Image with NativeImage.
Runtime source/accessibility processing
runtime/components/native-image.tsx, runtime/components/native-image.test.ts, runtime/index.ts, runtime/index.web.ts, runtime/types/react-native.d.ts, runtime/__tests__/*
Adds NativeImage resolver, processImageSourceProps/processImageAccessibilityProps, web shims, and type declarations, with unit tests.
Optimizer fixtures and unit tests
plugin/optimizers/image/__tests__/fixtures/*, plugin/optimizers/image/__tests__/index.test.ts
Adds Babel fixture pairs covering basic, accessibility, headers, source variants, style-derived props, and bail-out cases, plus platform-specific assertions.
Native attribute conformance tests
plugin/__tests__/native-valid-attributes.ts, plugin/__tests__/native-attribute-conformance.test.ts
Adds NATIVE_IMAGE_ATTRIBUTES extraction and Image conformance assertions.
Parity mock infrastructure
plugin/__tests__/parity/mocks/*, plugin/__tests__/parity/capture.tsx, boost.ts, wrapper.ts, vitest.config.parity.mts
Adds mocks for Image native hosts, feature flags, StyleSheet, and asset resolution; updates Vitest redirects.
Parity comparison tests
plugin/__tests__/parity/normalize.ts, parity.test.ts
Adds normalizeImage and Image/Image-dynamic parity test cases.
Fuzz generator and harness updates
plugin/__tests__/parity/fuzz/*
Extends fuzz Tag/vocabulary with Image, adds per-tag tracking and anti-vacuous checks.
Example benchmark update
apps/example/src/screens/benchmark.tsx
Adds an Image benchmark entry.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NativeImageComponent as native-image.tsx
  participant RNPlatform as Platform
  participant RuntimeIndex as runtime/index.ts
  participant Consumer as Optimized Component

  Consumer->>NativeImageComponent: import NativeImage
  NativeImageComponent->>RNPlatform: check Platform.OS
  alt web
    NativeImageComponent-->>Consumer: return RN Image
  else native
    NativeImageComponent->>NativeImageComponent: require internal host
    NativeImageComponent-->>Consumer: return default or fallback Image
  end
  Consumer->>RuntimeIndex: processImageSourceProps(props)
  RuntimeIndex-->>Consumer: normalized source/style/resizeMode
  Consumer->>RuntimeIndex: processImageAccessibilityProps(props)
  RuntimeIndex-->>Consumer: normalized accessibility props
Loading

Possibly related PRs

  • kuatsu/react-native-boost#23: Extends the same parity capture/test infrastructure (capture.tsx, parity.test.ts) introduced by this PR to validate <Image /> output.
  • kuatsu/react-native-boost#26: Image parity fuzz updates (fuzz.test.ts, generator.ts) build directly on the fuzzing infrastructure from this earlier PR.

Poem

A rabbit hopped through pixel fields so bright,
Found an <Image> tag and gave it flight—
No more heavy wrapper, just native and lean,
Tint and resize, the cleanest scene!
🐰🖼️ Thump-thump goes the optimized machine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the main change, introducing Image optimizer support, though it is a bit broad.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/react-native-boost/src/plugin/__tests__/parity/fuzz/vocabulary.ts (1)

343-348: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

crossOrigin/referrerPolicy never fuzz null/undefined.

Both request-header-driving props use plain fc.constantFrom(...) while sibling props like resizeMode/tintColor in the same vocab wrap values with withNullish(...). Since the PR explicitly adds "request headers derived from crossOrigin and referrerPolicy" handling, the fuzz suite won't exercise the nullish-value code path for these two props, leaving that branch of the request-header logic unverified by fuzzing.

♻️ Proposed fix
-  { name: 'crossOrigin', arb: fc.constantFrom('"anonymous"', '"use-credentials"'), disposition: 'request headers' },
+  { name: 'crossOrigin', arb: withNullish(fc.constantFrom('"anonymous"', '"use-credentials"')), disposition: 'request headers' },
   {
     name: 'referrerPolicy',
-    arb: fc.constantFrom('"origin"', '"no-referrer"', '"same-origin"'),
+    arb: withNullish(fc.constantFrom('"origin"', '"no-referrer"', '"same-origin"')),
     disposition: 'request headers',
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-boost/src/plugin/__tests__/parity/fuzz/vocabulary.ts`
around lines 343 - 348, The fuzz vocabulary for the request-header props is
missing nullish coverage for crossOrigin and referrerPolicy, so the new
request-header derivation path is not being exercised with null/undefined
inputs. Update the vocabulary entries in the vocabulary test fixture to match
sibling props like resizeMode and tintColor by wrapping the existing
fc.constantFrom values with withNullish, keeping the same symbols crossOrigin
and referrerPolicy so the nullish branch in the request-header logic is fuzzed
too.
packages/react-native-boost/src/plugin/__tests__/native-attribute-conformance.test.ts (1)

204-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

it.each skip pattern can mask coverage regressions for passthrough props.

if (!result?.optimized) return; (Line 210) causes the test to pass vacuously whenever the optimizer bails out — appropriate for IMAGE_WRAPPER_ONLY_PROPS (bail-out is expected/acceptable there), but for IMAGE_PASSTHROUGH_PROPS a regression that makes the optimizer unexpectedly bail on a legitimate native prop combination would silently pass this suite instead of failing. Only the single base-case assertion at Line 174 currently guarantees "exercises the optimized path" coverage.

Consider asserting result?.optimized === true for entries sourced from IMAGE_PASSTHROUGH_PROPS specifically (splitting the it.each or tagging entries), so a bail-out regression on a legitimate native prop is caught.

♻️ Proposed refactor to assert optimization for passthrough props
   describe('Image', () => {
-    it.each([...IMAGE_WRAPPER_ONLY_PROPS, ...IMAGE_PASSTHROUGH_PROPS])(
-      'leaves only native attributes on the host for <Image %s />',
-      (attributes) => {
-        const result = optimizeAndInspect(imageSource(attributes), imageOptimizer, 'Image', 'ios');
-        if (!result?.optimized) return; // bailed out: nothing reaches the native component
-        const leaked = result.attributes.filter((attribute) => !NATIVE_IMAGE_ATTRIBUTES.has(attribute));
-        expect(leaked, `optimized <Image ${attributes} /> leaks non-native attribute(s): ${leaked.join(', ')}`).toEqual(
-          []
-        );
-      }
-    );
+    it.each(IMAGE_WRAPPER_ONLY_PROPS)(
+      'leaves only native attributes on the host for <Image %s /> (if optimized)',
+      (attributes) => {
+        const result = optimizeAndInspect(imageSource(attributes), imageOptimizer, 'Image', 'ios');
+        if (!result?.optimized) return; // bailed out: nothing reaches the native component
+        const leaked = result.attributes.filter((attribute) => !NATIVE_IMAGE_ATTRIBUTES.has(attribute));
+        expect(leaked, `optimized <Image ${attributes} /> leaks non-native attribute(s): ${leaked.join(', ')}`).toEqual(
+          []
+        );
+      }
+    );
+
+    it.each(IMAGE_PASSTHROUGH_PROPS)(
+      'optimizes and leaves only native attributes on the host for <Image %s />',
+      (attributes) => {
+        const result = optimizeAndInspect(imageSource(attributes), imageOptimizer, 'Image', 'ios');
+        expect(result?.optimized, `<Image ${attributes} /> unexpectedly bailed out`).toBe(true);
+        const leaked = result!.attributes.filter((attribute) => !NATIVE_IMAGE_ATTRIBUTES.has(attribute));
+        expect(leaked, `optimized <Image ${attributes} /> leaks non-native attribute(s): ${leaked.join(', ')}`).toEqual(
+          []
+        );
+      }
+    );
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-boost/src/plugin/__tests__/native-attribute-conformance.test.ts`
around lines 204 - 217, The Image native-attribute conformance test is skipping
on optimizer bailouts in a way that can hide regressions for passthrough props.
Update the `describe('Image')` / `it.each([...IMAGE_WRAPPER_ONLY_PROPS,
...IMAGE_PASSTHROUGH_PROPS])` test so `IMAGE_PASSTHROUGH_PROPS` cases explicitly
require `optimizeAndInspect(...)` to return an optimized result instead of
returning early, while keeping the bail-out allowance only for wrapper-only
props. Split the table or tag entries so the `result?.optimized` expectation is
enforced for passthrough combinations and the leak check still uses
`NATIVE_IMAGE_ATTRIBUTES`.
packages/react-native-boost/src/runtime/components/native-image.test.ts (1)

33-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing happy-path test for successful native host resolution.

Both tests cover fallback scenarios (load failure, web short-circuit) but neither verifies that NativeImage resolves to the actual native host component when loading succeeds on a non-web platform — the primary intended behavior of resolveNativeImageComponent.

✅ Suggested additional test
+  it('resolves to the internal native host when loading succeeds on a non-web platform', async () => {
+    const { NativeImage } = await importNativeImage({ os: 'android' });
+
+    expect(NativeImage).toBe(nativeHost);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-boost/src/runtime/components/native-image.test.ts`
around lines 33 - 52, Add a happy-path test in NativeImage that verifies
resolveNativeImageComponent returns the loaded native host when
loadNativeComponent succeeds on a non-web platform. Reuse the existing
NativeImage describe block and the importNativeImage helper, but mock
loadNativeComponent to return the nativeHost default export without throwing,
then assert NativeImage is the nativeHost component and not reactNativeImage.
Keep the existing fallback tests unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-native-boost/src/plugin/__tests__/parity/normalize.ts`:
- Around line 30-69: The normalizeImage helper is deleting the very props that
the image parity cases are intended to verify. Update normalizeImage in the
parity normalize test to stop stripping width, height, crossOrigin,
referrerPolicy, alt, and the aria-* fields before the toEqual comparison, or
move those checks into explicit assertions so IMAGE_CASES can validate
request-header synthesis, ARIA normalization, and sizing behavior through
normalizeImage.

---

Nitpick comments:
In
`@packages/react-native-boost/src/plugin/__tests__/native-attribute-conformance.test.ts`:
- Around line 204-217: The Image native-attribute conformance test is skipping
on optimizer bailouts in a way that can hide regressions for passthrough props.
Update the `describe('Image')` / `it.each([...IMAGE_WRAPPER_ONLY_PROPS,
...IMAGE_PASSTHROUGH_PROPS])` test so `IMAGE_PASSTHROUGH_PROPS` cases explicitly
require `optimizeAndInspect(...)` to return an optimized result instead of
returning early, while keeping the bail-out allowance only for wrapper-only
props. Split the table or tag entries so the `result?.optimized` expectation is
enforced for passthrough combinations and the leak check still uses
`NATIVE_IMAGE_ATTRIBUTES`.

In `@packages/react-native-boost/src/plugin/__tests__/parity/fuzz/vocabulary.ts`:
- Around line 343-348: The fuzz vocabulary for the request-header props is
missing nullish coverage for crossOrigin and referrerPolicy, so the new
request-header derivation path is not being exercised with null/undefined
inputs. Update the vocabulary entries in the vocabulary test fixture to match
sibling props like resizeMode and tintColor by wrapping the existing
fc.constantFrom values with withNullish, keeping the same symbols crossOrigin
and referrerPolicy so the nullish branch in the request-header logic is fuzzed
too.

In `@packages/react-native-boost/src/runtime/components/native-image.test.ts`:
- Around line 33-52: Add a happy-path test in NativeImage that verifies
resolveNativeImageComponent returns the loaded native host when
loadNativeComponent succeeds on a non-web platform. Reuse the existing
NativeImage describe block and the importNativeImage helper, but mock
loadNativeComponent to return the nativeHost default export without throwing,
then assert NativeImage is the nativeHost component and not reactNativeImage.
Keep the existing fallback tests unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1834b295-5be9-43f5-81f1-df521c44e66f

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd9c2f and 0170c12.

📒 Files selected for processing (71)
  • apps/example/src/screens/benchmark.tsx
  • packages/react-native-boost/src/plugin/__tests__/native-attribute-conformance.test.ts
  • packages/react-native-boost/src/plugin/__tests__/native-valid-attributes.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/boost.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/capture.tsx
  • packages/react-native-boost/src/plugin/__tests__/parity/fuzz/fuzz.test.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/fuzz/generator.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/fuzz/vocabulary.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/ImageViewNativeComponent.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/NativeImageLoader.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/ReactNativeFeatureFlags.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/StyleSheet.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/TextInlineImageNativeComponent.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/boost-runtime.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/react-native.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/mocks/resolveAssetSource.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/normalize.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/parity.test.ts
  • packages/react-native-boost/src/plugin/__tests__/parity/vitest.config.parity.mts
  • packages/react-native-boost/src/plugin/__tests__/parity/wrapper.ts
  • packages/react-native-boost/src/plugin/index.ts
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/accessibility-props/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/accessibility-props/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/accessibility-spread-bails/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/accessibility-spread-bails/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/basic/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/basic/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/dynamic-request-headers-runtime/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/dynamic-request-headers-runtime/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/dynamic-source-runtime/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/dynamic-source-runtime/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/event-bails/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/event-bails/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/fallback-semantics/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/fallback-semantics/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/force-dynamic-runtime/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/force-dynamic-runtime/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/native-props/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/native-props/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/request-headers/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/request-headers/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/require-source/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/require-source/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/source-array/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/source-array/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/spread-wrapper-props-bails/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/spread-wrapper-props-bails/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/src-precedence/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/src-precedence/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/src-prop/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/src-prop/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/static-style-derived-props/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/static-style-derived-props/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/text-ancestor-bails/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/text-ancestor-bails/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/view-props/code.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/fixtures/view-props/output.js
  • packages/react-native-boost/src/plugin/optimizers/image/__tests__/index.test.ts
  • packages/react-native-boost/src/plugin/optimizers/image/index.ts
  • packages/react-native-boost/src/plugin/types/index.ts
  • packages/react-native-boost/src/plugin/utils/common/base.ts
  • packages/react-native-boost/src/plugin/utils/generate-test-plugin.ts
  • packages/react-native-boost/src/runtime/__tests__/index.test.ts
  • packages/react-native-boost/src/runtime/__tests__/mocks/ImageViewNativeComponent.ts
  • packages/react-native-boost/src/runtime/__tests__/mocks/react-native.ts
  • packages/react-native-boost/src/runtime/components/native-image.test.ts
  • packages/react-native-boost/src/runtime/components/native-image.tsx
  • packages/react-native-boost/src/runtime/index.ts
  • packages/react-native-boost/src/runtime/index.web.ts
  • packages/react-native-boost/src/runtime/types/react-native.d.ts
  • packages/react-native-boost/vitest.config.ts

@mfkrause

mfkrause commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for your work on this! I'll have a look at it over the weekend.

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.

2 participants