diff --git a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js index d05f0b7aba3c97..3058f7154048ce 100644 --- a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -293,7 +293,7 @@ describe('resolveAssetSource', () => { Platform.OS = 'android'; }); - it('uses bundled source, event when js is sideloaded', () => { + it('uses bundled source, even when js is sideloaded', () => { resolveAssetSource.setCustomSourceTransformer(resolver => resolver.resourceIdentifierWithoutScale(), ); @@ -319,6 +319,64 @@ describe('resolveAssetSource', () => { ); }); + it('can chain multiple custom source transformers', () => { + resolveAssetSource.addCustomSourceTransformer(resolver => { + if (resolver.asset.type === 'gif') { + return resolver.fromSource(`my_gif_file`); + } + return null; + }); + + resolveAssetSource.addCustomSourceTransformer(resolver => { + if (resolver.asset.type === 'png') { + return resolver.fromSource(`my_png_file`); + } + return null; + }); + + const pngAsset = { + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/AwesomeModule/Subdir', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: '!@Logo#1_\u20ac', + type: 'png', + }; + + expectResolvesAsset(pngAsset, { + __packager_asset: true, + width: 100, + height: 200, + uri: 'my_png_file', + scale: 1, + }); + + expectResolvesAsset( + {...pngAsset, type: 'gif'}, + { + __packager_asset: true, + width: 100, + height: 200, + uri: 'my_gif_file', + scale: 1, + }, + ); + + expectResolvesAsset( + {...pngAsset, type: 'jpg'}, + { + __packager_asset: true, + width: 100, + height: 200, + uri: 'file:///sdcard/Path/To/Simulator/drawable-mdpi/awesomemodule_subdir_logo1_.jpg', + scale: 1, + }, + ); + }); + it('allows any customization', () => { resolveAssetSource.setCustomSourceTransformer(resolver => resolver.fromSource('TEST'), diff --git a/packages/react-native/Libraries/Image/resolveAssetSource.js b/packages/react-native/Libraries/Image/resolveAssetSource.js index 38d36edd17cbce..423b3de5c366ad 100644 --- a/packages/react-native/Libraries/Image/resolveAssetSource.js +++ b/packages/react-native/Libraries/Image/resolveAssetSource.js @@ -8,22 +8,25 @@ * @flow strict-local */ -// Resolves an asset into a `source` for `Image`. - -'use strict'; +// Utilities for resolving an asset into a `source` for e.g. `Image` import type {ResolvedAssetSource} from './AssetSourceResolver'; import type {ImageSource} from './ImageSource'; import SourceCode from '../NativeModules/specs/NativeSourceCode'; +import AssetSourceResolver from './AssetSourceResolver'; +import {pickScale} from './AssetUtils'; +import {getAssetByID} from '@react-native/assets-registry/registry'; -const AssetSourceResolver = require('./AssetSourceResolver'); -const {pickScale} = require('./AssetUtils'); -const AssetRegistry = require('@react-native/assets-registry/registry'); - -let _customSourceTransformer, _serverURL, _scriptURL; +type CustomSourceTransformer = ( + resolver: AssetSourceResolver, +) => ?ResolvedAssetSource; +let _customSourceTransformers: Array = []; +let _serverURL: ?string; +let _scriptURL: ?string; let _sourceCodeScriptURL: ?string; + function getSourceCodeScriptURL(): ?string { if (_sourceCodeScriptURL != null) { return _sourceCodeScriptURL; @@ -77,10 +80,25 @@ function getScriptURL(): ?string { return _scriptURL; } +/** + * `transformer` can optionally be used to apply a custom transformation when + * resolving an asset source. This methods overrides all other custom transformers + * that may have been previously registered. + */ function setCustomSourceTransformer( - transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource, + transformer: CustomSourceTransformer, ): void { - _customSourceTransformer = transformer; + _customSourceTransformers = [transformer]; +} + +/** + * Adds a `transformer` into the chain of custom source transformers, which will + * be applied in the order registered, until one returns a non-null value. + */ +function addCustomSourceTransformer( + transformer: CustomSourceTransformer, +): void { + _customSourceTransformers.push(transformer); } /** @@ -94,7 +112,7 @@ function resolveAssetSource(source: ?ImageSource): ?ResolvedAssetSource { return source; } - const asset = AssetRegistry.getAssetByID(source); + const asset = getAssetByID(source); if (!asset) { return null; } @@ -104,12 +122,21 @@ function resolveAssetSource(source: ?ImageSource): ?ResolvedAssetSource { getScriptURL(), asset, ); - if (_customSourceTransformer) { - return _customSourceTransformer(resolver); + + // Apply (chained) custom source transformers, if any + if (_customSourceTransformers) { + for (const customSourceTransformer of _customSourceTransformers) { + const transformedSource = customSourceTransformer(resolver); + if (transformedSource != null) { + return transformedSource; + } + } } + return resolver.defaultAsset(); } resolveAssetSource.pickScale = pickScale; resolveAssetSource.setCustomSourceTransformer = setCustomSourceTransformer; +resolveAssetSource.addCustomSourceTransformer = addCustomSourceTransformer; module.exports = resolveAssetSource;