From ef27b57930fe5a0aa1dd11af2ca11784289c697a Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:51:37 +0000 Subject: [PATCH] fix(repack): pass inlined asset scales to pickScale in ascending order --- .changeset/inline-assets-scale-order.md | 5 ++ .../__tests__/inlineAssets.test.ts | 47 +++++++++++++++++++ .../src/loaders/assetsLoader/inlineAssets.ts | 5 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .changeset/inline-assets-scale-order.md create mode 100644 packages/repack/src/loaders/assetsLoader/__tests__/inlineAssets.test.ts diff --git a/.changeset/inline-assets-scale-order.md b/.changeset/inline-assets-scale-order.md new file mode 100644 index 000000000..10d11f4dd --- /dev/null +++ b/.changeset/inline-assets-scale-order.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": patch +--- + +Pass scales of inlined assets to `AssetSourceResolver.pickScale` in ascending order. Previously, assets with fractional scales such as `@1.5x` resolved to the wrong variant, for example the `@1.5x` image on a device with a pixel ratio of 3.5. diff --git a/packages/repack/src/loaders/assetsLoader/__tests__/inlineAssets.test.ts b/packages/repack/src/loaders/assetsLoader/__tests__/inlineAssets.test.ts new file mode 100644 index 000000000..63aca7461 --- /dev/null +++ b/packages/repack/src/loaders/assetsLoader/__tests__/inlineAssets.test.ts @@ -0,0 +1,47 @@ +import vm from 'node:vm'; +import { inlineAssets } from '../inlineAssets.js'; +import type { Asset } from '../types.js'; + +// Same logic as `pickScale` from `react-native/Libraries/Image/AssetUtils`, +// which expects `scales` to be sorted in ascending order. +function pickScale(scales: number[], deviceScale: number) { + for (const scale of scales) { + if (scale >= deviceScale) { + return scale; + } + } + return scales[scales.length - 1] || 1; +} + +function evaluateInlinedAsset(code: string, pixelRatio: number) { + const module = { exports: {} as { scale?: number } }; + const require = (request: string) => + request === 'react-native' + ? { PixelRatio: { get: () => pixelRatio } } + : { pickScale }; + + vm.runInNewContext(code, { module, require }); + return module.exports; +} + +// assets are sorted by scale in ascending order, as in the assets loader +const assets: Asset[] = [0.75, 1, 1.5, 2, 3].map((scale) => ({ + data: Buffer.from(`star@${scale}x`), + dimensions: { width: 10, height: 10 }, + filename: `star@${scale}x.png`, + scale, +})); + +describe('inlineAssets', () => { + it.each([ + [0.75, 0.75], + [1, 1], + [1.5, 1.5], + [2.625, 3], + [3.5, 3], + ])('picks the right scale for pixel ratio %s', (pixelRatio, expected) => { + const code = inlineAssets({ assets, resourcePath: 'star.png' }); + + expect(evaluateInlinedAsset(code, pixelRatio).scale).toBe(expected); + }); +}); diff --git a/packages/repack/src/loaders/assetsLoader/inlineAssets.ts b/packages/repack/src/loaders/assetsLoader/inlineAssets.ts index 9dd776c5e..c7cc954de 100644 --- a/packages/repack/src/loaders/assetsLoader/inlineAssets.ts +++ b/packages/repack/src/loaders/assetsLoader/inlineAssets.ts @@ -31,7 +31,10 @@ export function inlineAssets({ {} as Record ); - const scales = JSON.stringify(Object.keys(sourceSet).map(Number)); + // `pickScale` expects scales in ascending order. `assets` are already sorted, + // while `Object.keys` would list integer keys (1, 2, 3) before fractional + // ones (0.75, 1.5). + const scales = JSON.stringify(assets.map(({ scale }) => scale)); /** * To enable scale resolution in runtime we need to import PixelRatio & AssetSourceResolver