From fcfd82ede8112138f98778f24fbbdac0c2dbcd8d Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Tue, 6 Feb 2024 04:47:27 -0800 Subject: [PATCH] Allow to chain custom transformers when resolving asset sources Summary: ## Changelog: [Internal]- This adds an ability to have multiple custom source transformers when resolving asset sources, which is beneficial in some scenarios. The transformers are chained, being executed in order they are registered, until one of them returns a non-null value. If none does, then the default one is returned. Differential Revision: D53472320 --- .../__tests__/resolveAssetSource-test.js | 60 ++++++++++++++++++- .../Libraries/Image/resolveAssetSource.js | 53 ++++++++++++---- 2 files changed, 99 insertions(+), 14 deletions(-) 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;