Files
Janic Duplessis 74b984729b Fix asset catalog scale-to-file pairing for assets with non-standard scales (#57825)
Summary:
`saveAssets` filters an asset's scales through `filterPlatformAssetScales`, but `getImageSet` indexes the unfiltered `asset.files`, so an asset with scales `[1, 1.5, 2, 3]` gets the 1.5x file in its 2x catalog slot and the 2x file in its 3x slot: `Assets.car` ships the wrong images. An asset with no standard scale at all (only `1.5x`, say) produces an imageset actool silently drops from the car, which makes the image unloadable when `RCTUseAssetCatalog` is on, since the catalog runtime has no filesystem fallback (https://github.com/react/react-native/issues/30129).

Fix, in `assetCatalogIOS.js`:

- Each catalog slot (1x/2x/3x) is paired with its own file.
- An asset with no valid scale maps its closest variant into the nearest valid slot, the same "closest larger" rule loose files already get, and warns in the build log. Every imageset now holds at least one rendition actool will compile.

Related: expo/expo#48525 applies the same fix to Expo CLI's mirrored implementation.

## Changelog:

[IOS] [FIXED] - Asset catalog imagesets paired wrong files for assets with non-standard scales

Pull Request resolved: https://github.com/react/react-native/pull/57825

Test Plan:
New unit tests in `assetCatalogIOS-test.js`: standard 1x/2x/3x pairing, mixed `[1, 1.5, 2, 3]` (regression for the file shift), fractional-only `[1.5]`, and `[4]` clamping to the 3x slot.

```
yarn jest packages/community-cli-plugin/src/commands/bundle
Tests: 18 passed, 18 total
```

### End-to-end

Bundled a test app through the real pipeline (Metro → `saveAssets` → actool → `assetutil --info` on the compiled `Assets.car`) with two assets: `logo` at scales `[1, 1.5, 2, 3]` (100/150/200/300 px) and `star` with only a `1.5x` file (150 px).

| Rendition in `Assets.car` | before | after |
|---|---|---|
| `img_logo` 1x | 100 px | 100 px |
| `img_logo` 2x | **150 px (the 1.5x file)** | 200 px |
| `img_logo` 3x | **200 px (the 2x file)** | 300 px |
| `img_star` | **absent (actool dropped the `1.5x` imageset)** | 150 px in the 2x slot, with a build-log warning |

Running on an iPhone 17 Pro simulator (3x), same app built with the buggy and fixed CLI — the labeled tiles show which file the catalog actually served, and `star` goes from missing to rendering:

| Before | After |
|---|---|
| <img src="https://github.com/user-attachments/assets/4ebdbd33-74ec-4731-a2c4-f1a2d9b5838b" width="320" /> | <img src="https://github.com/user-attachments/assets/cd461b0a-5763-4388-8771-1ae6df383817" width="320" /> |

<details>
<summary>Repro app used for the screenshots</summary>

Built `private/helloworld` (Release, simulator) with `RCTUseAssetCatalog` set to `true` in its Info.plist, and these assets in `img/`, where each file is a solid tile with its scale label and pixel size baked into the image so a screenshot shows exactly which file got served: `logo.png` (100px, "1x"), `logo@1.5x.png` (150px, "1.5x"), `logo@2x.png` (200px, "2x"), `logo@3x.png` (300px, "3x"), and `star@1.5x.png` (150px, "STAR") with no other variants.

```js
// index.js
import React from 'react';
import {AppRegistry, Image, Text, View, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  root: {flex: 1, backgroundColor: 'https://github.com/react/react-native/issues/111', alignItems: 'center', justifyContent: 'center'},
  label: {color: '#fff', fontSize: 16, marginTop: 24, marginBottom: 8, fontWeight: '600'},
  box: {width: 100, height: 100, borderWidth: 2, borderColor: 'https://github.com/react/react-native/issues/666'},
  img: {width: 100, height: 100},
});

const App = () => (
  <View style={styles.root}>
    <Text style={styles.label}>logo.png (has 1x/1.5x/2x/3x)</Text>
    <View style={styles.box}>
      <Image style={styles.img} source={require('./img/logo.png')} />
    </View>
    <Text style={styles.label}>star.png (only 1.5x)</Text>
    <View style={styles.box}>
      <Image style={styles.img} source={require('./img/star.png')} />
    </View>
  </View>
);

AppRegistry.registerComponent('HelloWorld', () => App);
```

On the 3x simulator JS resolves `logo` to the 3x variant, so the tile that renders is the file the catalog's 3x slot actually contains: the 2x-labeled tile before the fix, the 3x tile after. `star` resolves to its only variant (`1.5x`); before the fix its imageset is dropped by actool and the box renders empty.

</details>

Reviewed By: christophpurrer

Differential Revision: D116956049

Pulled By: javache

fbshipit-source-id: 4405c235098ea7f8a4ebc03ca6876a3981894e71
2026-08-24 08:55:47 -07:00
..

@react-native/community-cli-plugin

npm npm downloads

This is an internal dependency of React Native. Please don't depend on it directly.

CLI entry points supporting core React Native development features.

Formerly @react-native-community/cli-plugin-metro.

Commands

start

Start the React Native development server.

Usage

npx @react-native-community/cli start [options]

Options

Option Description
--port <number> Set the server port.
--host <string> Set the server host.
--projectRoot <path> Set the path to the project root.
--watchFolders <list> Specify additional folders to be added to the watch list.
--assetPlugins <list> Specify additional asset plugins.
--sourceExts <list> Specify additional source extensions to bundle.
--max-workers <number> Set the maximum number of workers the worker-pool will spawn for transforming files. Defaults to the number of the cores available on your machine.
--transformer <string> Specify a custom transformer.
--reset-cache Remove cached files.
--custom-log-reporter-path <string> Specify a module path exporting a replacement for TerminalReporter.
--https Enable HTTPS connections.
--key <path> Specify path to a custom SSL key.
--cert <path> Specify path to a custom SSL cert.
--config <string> Path to the CLI configuration file.
--no-interactive Disable interactive mode.
--client-logs [Deprecated] Enable plain text JavaScript log streaming for all connected apps.

bundle

Build the bundle for the provided JavaScript entry file.

Usage

npx @react-native-community/cli bundle --entry-file <path> [options]

Options

Option Description
--entry-file <path> Set the path to the root JavaScript entry file.
--platform <string> Set the target platform (either "android" or "ios"). Defaults to "ios".
--transformer <string> Specify a custom transformer.
--dev [boolean] If false, warnings are disabled and the bundle is minified. Defaults to true.
--minify [boolean] Allows overriding whether bundle is minified. Defaults to false if --dev is set. Disabling minification can be useful for speeding up production builds for testing purposes.
--bundle-output <string> Specify the path to store the resulting bundle.
--bundle-encoding <string> Specify the encoding for writing the bundle (https://nodejs.org/api/buffer.html#buffer_buffer).
--resolver-option <string...> Custom resolver options of the form key=value. URL-encoded. May be specified multiple times.
--sourcemap-output <string> Specify the path to store the source map file for the resulting bundle.
--sourcemap-sources-root <string> Set the root path for source map entries.
--sourcemap-use-absolute-path Report SourceMapURL using its full path.
--max-workers <number> Set the maximum number of workers the worker-pool will spawn for transforming files. Defaults to the number of the cores available on your machine.
--assets-dest <string> Specify the directory path for storing assets referenced in the bundle.
--reset-cache Remove cached files.
--read-global-cache Attempt to fetch transformed JS code from the global cache, if configured. Defaults to false.
--config <string> Path to the CLI configuration file.

codegen

Run the React Native codegen, generating native boilerplate from JS spec files.

Usage

npx @react-native-community/cli codegen [options]

Options

Option Description
--path <path> Path to the React Native project root. Defaults to the current working directory.
--platform <string> Target platform. Supported values: "android", "ios", "all". Defaults to "all".
--outputPath <path> Path where generated artifacts will be output to.
--source <string> Whether the script is invoked from an app or a library. Defaults to "app".

spm [action]

Set up or maintain Swift Package Manager support for the iOS/macOS app. Actions: add, update, deinit, scaffold. With no action: add (or update if SPM is already set up).

Usage

npx @react-native-community/cli spm [action] [options]

Options

Option Description
--version <string> React Native version (e.g. 0.80.0). Defaults to the version in node_modules/react-native/package.json.
--yes Skip the dirty-pbxproj confirmation prompt.
--xcodeproj <path> [add] Path to the .xcodeproj to inject SPM packages into (disambiguates when several exist).
--productName <string> [add] App target to inject into (disambiguates when several exist).
--deintegrate [add] Run pod deintegrate and strip React Native from the Podfile before injecting (CocoaPods → SwiftPM migration).
--artifacts <path> [advanced] Local artifact root containing complete debug/ and release/ slots.
--download <string> [advanced] Artifact download policy: auto (default), skip, or force.
--skipCodegen [advanced] Skip the react-native codegen step.

Contributing

Changes to this package can be made locally and tested against the rn-tester app, per the Contributing guide. During development, this package is automatically run from source with no build step.