From 62b1335fc915f2d3cb1125258fd99f6deb29bf42 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Tue, 30 Jun 2026 06:09:33 -0700 Subject: [PATCH] Create @react-native/asset-utils package (#57368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: **Context** This stack intends to relocate the `AssetRegistry` API from `react-native/assets-registry` into `react-native` — addressing runtime safety and public deep imports issues (1.0 and JS Stable API blockers). **This diff** Create a new `react-native/asset-utils` package, intended for internal/framework use (read: **will not be an end user concern**). This re-houses `react-native/assets-registry/path-support.js`. Stacked with the next two diffs, this contributes to the 0.87 objective to delete `react-native/assets-registry` towards install-layout-safe behaviour and JS deep import removal. - (Temporarily, this leaves two copies of `path-support.js` until I enact the package removal down the stack.) This change also enables us to de-duplicate `assetPathUtils.js` inside `community-cli-plugin` and use one source of truth. - Additionally, we have fbsource consumers of this util that cannot have a circular Buck dep on `react-native`, so the main package was not a suitable relocation point (secondarily to not adding an awkward new public API here). **Changes** - Add `react-native/asset-utils` (`packages/asset-utils/`): `src/AssetPathUtils.js` containing Android path helpers and tests. - De-duplicate usages in `community-cli-plugin`: delete `src/commands/bundle/assetPathUtils.js`. Changelog: [General][Added] - Introduce `react-native/asset-utils` package (relocates Android path utils for libraries/frameworks) Differential Revision: D110045272 --- packages/asset-utils/README.md | 23 +++++ packages/asset-utils/package.json | 31 ++++++ .../asset-utils/src/AndroidPathUtils.d.ts | 23 +++++ packages/asset-utils/src/AndroidPathUtils.js | 93 ++++++++++++++++++ .../src/__tests__/AndroidPathUtils-test.js | 72 ++++++++++++++ packages/asset-utils/src/index.d.ts | 10 ++ packages/asset-utils/src/index.js | 17 ++++ packages/community-cli-plugin/package.json | 1 + .../filterPlatformAssetScales-test.js | 2 +- .../__tests__/getAssetDestPathAndroid-test.js | 4 +- .../src/commands/bundle/assetCatalogIOS.js | 4 +- .../src/commands/bundle/assetPathUtils.js | 95 ------------------- .../commands/bundle/createKeepFileAsync.js | 13 ++- .../bundle/getAssetDestPathAndroid.js | 14 +-- .../commands/bundle/getAssetDestPathIOS.js | 2 +- .../Libraries/Image/AssetSourceResolver.js | 8 +- packages/react-native/metro.config.js | 1 + packages/react-native/package.json | 1 + packages/rn-tester/metro.config.js | 1 + 19 files changed, 299 insertions(+), 116 deletions(-) create mode 100644 packages/asset-utils/README.md create mode 100644 packages/asset-utils/package.json create mode 100644 packages/asset-utils/src/AndroidPathUtils.d.ts create mode 100644 packages/asset-utils/src/AndroidPathUtils.js create mode 100644 packages/asset-utils/src/__tests__/AndroidPathUtils-test.js create mode 100644 packages/asset-utils/src/index.d.ts create mode 100644 packages/asset-utils/src/index.js delete mode 100644 packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js diff --git a/packages/asset-utils/README.md b/packages/asset-utils/README.md new file mode 100644 index 000000000000..ebcd7e938e64 --- /dev/null +++ b/packages/asset-utils/README.md @@ -0,0 +1,23 @@ +# @react-native/asset-utils + +[![npm]](https://www.npmjs.com/package/@react-native/asset-utils) [![npm downloads]](https://www.npmjs.com/package/@react-native/asset-utils) + +[npm]: https://img.shields.io/npm/v/@react-native/asset-utils.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/asset-utils.svg + +Android resource-path helpers used when copying React Native assets into `drawable-*` / `raw` folders. Consumed by bundling and build tooling; most apps never import this directly. + +## API + +```js +import { + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; +``` + +| Export | Signature | Notes | +|---|---|---| +| `getAndroidResourceFolderName` | `(asset: PackagerAsset, scale: number) => string` | e.g. `drawable-xhdpi`; non-drawable types resolve to `raw` | +| `getAndroidResourceIdentifier` | `(asset: PackagerAsset) => string` | Sanitised resource name | +| `drawableFileTypes` | `Set` | Asset types that map to a `drawable-*` folder | diff --git a/packages/asset-utils/package.json b/packages/asset-utils/package.json new file mode 100644 index 000000000000..bec6ea4d6931 --- /dev/null +++ b/packages/asset-utils/package.json @@ -0,0 +1,31 @@ +{ + "name": "@react-native/asset-utils", + "version": "0.87.0-main", + "description": "Asset path utilities for React Native.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/react/react-native.git", + "directory": "packages/asset-utils" + }, + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/asset-utils#readme", + "keywords": [ + "react-native" + ], + "bugs": "https://github.com/react/react-native/issues", + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "exports": { + ".": "./src/index.js", + "./package.json": "./package.json" + }, + "files": [ + "src", + "README.md", + "!**/__docs__/**", + "!**/__fixtures__/**", + "!**/__mocks__/**", + "!**/__tests__/**" + ] +} diff --git a/packages/asset-utils/src/AndroidPathUtils.d.ts b/packages/asset-utils/src/AndroidPathUtils.d.ts new file mode 100644 index 000000000000..854eecc3338f --- /dev/null +++ b/packages/asset-utils/src/AndroidPathUtils.d.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +export type PackagerAsset = Readonly<{ + httpServerLocation: string; + name: string; + type: string; +}>; + +export function getAndroidResourceFolderName( + asset: PackagerAsset, + scale: number, +): string; + +export function getAndroidResourceIdentifier(asset: PackagerAsset): string; + +export const drawableFileTypes: Set; diff --git a/packages/asset-utils/src/AndroidPathUtils.js b/packages/asset-utils/src/AndroidPathUtils.js new file mode 100644 index 000000000000..3b76e82aab02 --- /dev/null +++ b/packages/asset-utils/src/AndroidPathUtils.js @@ -0,0 +1,93 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +/*:: +// Conforms to the `PackagerAsset` type from `react-native`. +export type PackagerAsset = Readonly<{ + httpServerLocation: string, + name: string, + type: string, + ... +}>; +*/ + +const androidScaleSuffix /*: {[string]: string} */ = { + '0.75': 'ldpi', + '1': 'mdpi', + '1.5': 'hdpi', + '2': 'xhdpi', + '3': 'xxhdpi', + '4': 'xxxhdpi', +}; + +const ANDROID_BASE_DENSITY = 160; + +// FIXME: Using number to represent discrete scale numbers is fragile in +// essence because of floating point number imprecision. +function getAndroidAssetSuffix(scale /*: number */) /*: string */ { + if (scale.toString() in androidScaleSuffix) { + return androidScaleSuffix[scale.toString()]; + } + + // NOTE: Android Gradle Plugin does not fully support the nnndpi format. + // See https://issuetracker.google.com/issues/72884435 + if (Number.isFinite(scale) && scale > 0) { + return Math.round(scale * ANDROID_BASE_DENSITY) + 'dpi'; + } + + throw new Error('no such scale ' + scale.toString()); +} + +// See https://developer.android.com/guide/topics/resources/drawable-resource.html +const drawableFileTypes /*: Set */ = new Set([ + 'gif', + 'heic', + 'heif', + 'jpeg', + 'jpg', + 'ktx', + 'png', + 'webp', + 'xml', +]); + +function getAndroidResourceFolderName( + asset /*: PackagerAsset */, + scale /*: number */, +) /*: string */ { + if (!drawableFileTypes.has(asset.type)) { + return 'raw'; + } + + return 'drawable-' + getAndroidAssetSuffix(scale); +} + +function getAndroidResourceIdentifier( + asset /*: PackagerAsset */, +) /*: string */ { + return (getBasePath(asset) + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^(?:assets|assetsunstable_path)_/, ''); // Remove "assets_" or "assetsunstable_path_" prefix +} + +function getBasePath(asset /*: PackagerAsset */) /*: string */ { + const basePath = asset.httpServerLocation; + return basePath.startsWith('/') ? basePath.slice(1) : basePath; +} + +module.exports = { + drawableFileTypes, + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +}; diff --git a/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js b/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js new file mode 100644 index 000000000000..2496c1d9ee01 --- /dev/null +++ b/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import {getAndroidResourceFolderName} from '../AndroidPathUtils'; + +const DRAWABLE_ASSET = { + httpServerLocation: '/assets/', + name: 'foo', + type: 'png', +}; + +const NON_DRAWABLE_ASSET = { + httpServerLocation: '/assets/', + name: 'foo', + type: 'txt', +}; + +describe('getAndroidResourceFolderName', () => { + test('supports the six primary density buckets', () => { + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 0.75)).toBe( + 'drawable-ldpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1)).toBe( + 'drawable-mdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.5)).toBe( + 'drawable-hdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 2)).toBe( + 'drawable-xhdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 3)).toBe( + 'drawable-xxhdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 4)).toBe( + 'drawable-xxxhdpi', + ); + }); + + test('supports nonstandard densities', () => { + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.25)).toBe( + 'drawable-200dpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.66)).toBe( + 'drawable-266dpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.33)).toBe( + 'drawable-213dpi', + ); // ~tvdpi + }); + + test('throws if the density cannot be processed', () => { + expect(() => getAndroidResourceFolderName(DRAWABLE_ASSET, -1)).toThrow(); + expect(() => getAndroidResourceFolderName(DRAWABLE_ASSET, 0)).toThrow(); + expect(() => + getAndroidResourceFolderName(DRAWABLE_ASSET, Infinity), + ).toThrow(); + }); + + test('returns "raw" for non-drawables', () => { + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 0.75)).toBe('raw'); + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 1)).toBe('raw'); + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 1.25)).toBe('raw'); + }); +}); diff --git a/packages/asset-utils/src/index.d.ts b/packages/asset-utils/src/index.d.ts new file mode 100644 index 000000000000..c67af666a8a3 --- /dev/null +++ b/packages/asset-utils/src/index.d.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +export * from './AndroidPathUtils'; diff --git a/packages/asset-utils/src/index.js b/packages/asset-utils/src/index.js new file mode 100644 index 000000000000..4fdff4702d04 --- /dev/null +++ b/packages/asset-utils/src/index.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +/*:: +export type {PackagerAsset} from './AndroidPathUtils'; +*/ + +module.exports = require('./AndroidPathUtils'); diff --git a/packages/community-cli-plugin/package.json b/packages/community-cli-plugin/package.json index 966a8e8385d7..1b5a3327ffaf 100644 --- a/packages/community-cli-plugin/package.json +++ b/packages/community-cli-plugin/package.json @@ -31,6 +31,7 @@ "prepack": "node ../../scripts/build/prepack.js" }, "dependencies": { + "@react-native/asset-utils": "0.87.0-main", "@react-native/dev-middleware": "0.87.0-main", "debug": "^4.4.0", "invariant": "^2.2.4", diff --git a/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js b/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js index 5973fdceb678..d5ff14434f13 100644 --- a/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js +++ b/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js @@ -10,7 +10,7 @@ import filterPlatformAssetScales from '../filterPlatformAssetScales'; -jest.dontMock('../filterPlatformAssetScales').dontMock('../assetPathUtils'); +jest.dontMock('../filterPlatformAssetScales'); describe('filterPlatformAssetScales', () => { test('removes everything but 2x and 3x for iOS', () => { diff --git a/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js b/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js index c2198025d691..55d0456bc803 100644 --- a/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js +++ b/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js @@ -12,7 +12,9 @@ import getAssetDestPathAndroid from '../getAssetDestPathAndroid'; const path = require('path'); -jest.dontMock('../getAssetDestPathAndroid').dontMock('../assetPathUtils'); +jest + .dontMock('../getAssetDestPathAndroid') + .dontMock('@react-native/asset-utils'); describe('getAssetDestPathAndroid', () => { test('should use the right destination folder', () => { diff --git a/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js b/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js index eefc3c6d5ad9..64577c397e0c 100644 --- a/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js +++ b/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js @@ -10,7 +10,7 @@ import type {AssetData} from 'metro'; -import assetPathUtils from './assetPathUtils'; +import {getAndroidResourceIdentifier} from '@react-native/asset-utils'; import fs from 'fs'; import path from 'path'; @@ -33,7 +33,7 @@ export function getImageSet( asset: AssetData, scales: ReadonlyArray, ): ImageSet { - const fileName = assetPathUtils.getResourceIdentifier(asset); + const fileName = getAndroidResourceIdentifier(asset); return { basePath: path.join(catalogDir, `${fileName}.imageset`), files: scales.map((scale, idx) => { diff --git a/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js b/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js deleted file mode 100644 index 1ac88c6c01ec..000000000000 --- a/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -export type PackagerAsset = Readonly<{ - httpServerLocation: string, - name: string, - type: string, - ... -}>; - -/** - * FIXME: using number to represent discrete scale numbers is fragile in essence because of - * floating point numbers imprecision. - */ -function getAndroidAssetSuffix(scale: number): string { - switch (scale) { - case 0.75: - return 'ldpi'; - case 1: - return 'mdpi'; - case 1.5: - return 'hdpi'; - case 2: - return 'xhdpi'; - case 3: - return 'xxhdpi'; - case 4: - return 'xxxhdpi'; - default: - return ''; - } -} - -// See https://developer.android.com/guide/topics/resources/drawable-resource.html -const drawableFileTypes: Set = new Set([ - 'gif', - 'heic', - 'heif', - 'jpeg', - 'jpg', - 'png', - 'webp', - 'xml', -]); - -function getAndroidResourceFolderName( - asset: PackagerAsset, - scale: number, -): string { - if (!drawableFileTypes.has(asset.type)) { - return 'raw'; - } - const suffix = getAndroidAssetSuffix(scale); - if (!suffix) { - throw new Error( - `Don't know which android drawable suffix to use for asset: ${JSON.stringify( - asset, - )}`, - ); - } - const androidFolder = `drawable-${suffix}`; - return androidFolder; -} - -function getResourceIdentifier(asset: PackagerAsset): string { - const folderPath = getBasePath(asset); - return `${folderPath}/${asset.name}` - .toLowerCase() - .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, '') // Remove illegal chars - .replace(/^(?:assets|assetsunstable_path)_/, ''); // Remove "assets_" or "assetsunstable_path_" prefix -} - -function getBasePath(asset: PackagerAsset): string { - let basePath = asset.httpServerLocation; - if (basePath[0] === '/') { - basePath = basePath.substr(1); - } - return basePath; -} - -export default { - drawableFileTypes, - getAndroidAssetSuffix, - getAndroidResourceFolderName, - getResourceIdentifier, - getBasePath, -}; diff --git a/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js b/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js index 479c1bdf0143..29050c74e79c 100644 --- a/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js +++ b/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js @@ -10,7 +10,10 @@ import type {AssetData} from 'metro'; -import assetPathUtils from './assetPathUtils'; +import { + drawableFileTypes, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; import fs from 'fs'; import path from 'path'; @@ -23,12 +26,8 @@ async function createKeepFileAsync( } const assetsList = []; for (const asset of assets) { - const prefix = assetPathUtils.drawableFileTypes.has(asset.type) - ? 'drawable' - : 'raw'; - assetsList.push( - `@${prefix}/${assetPathUtils.getResourceIdentifier(asset)}`, - ); + const prefix = drawableFileTypes.has(asset.type) ? 'drawable' : 'raw'; + assetsList.push(`@${prefix}/${getAndroidResourceIdentifier(asset)}`); } const keepPath = path.join(outputDirectory, 'raw/keep.xml'); const content = `\n`; diff --git a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js index 6a8c913ce844..d847c6e77087 100644 --- a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js +++ b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js @@ -8,17 +8,17 @@ * @format */ -import type {PackagerAsset} from './assetPathUtils'; +import type {PackagerAsset} from '@react-native/asset-utils'; -import assetPathUtils from './assetPathUtils'; +import { + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; import path from 'path'; function getAssetDestPathAndroid(asset: PackagerAsset, scale: number): string { - const androidFolder = assetPathUtils.getAndroidResourceFolderName( - asset, - scale, - ); - const fileName = assetPathUtils.getResourceIdentifier(asset); + const androidFolder = getAndroidResourceFolderName(asset, scale); + const fileName = getAndroidResourceIdentifier(asset); return path.join(androidFolder, `${fileName}.${asset.type}`); } diff --git a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js index 8f4cb0926a28..5c3e37c1dff7 100644 --- a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js +++ b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js @@ -8,7 +8,7 @@ * @format */ -import type {PackagerAsset} from './assetPathUtils'; +import type {PackagerAsset} from '@react-native/asset-utils'; import path from 'path'; diff --git a/packages/react-native/Libraries/Image/AssetSourceResolver.js b/packages/react-native/Libraries/Image/AssetSourceResolver.js index 2b1e9647608a..9bbfc9f47bfc 100644 --- a/packages/react-native/Libraries/Image/AssetSourceResolver.js +++ b/packages/react-native/Libraries/Image/AssetSourceResolver.js @@ -42,8 +42,7 @@ const {pickScale} = require('./AssetUtils'); const { getAndroidResourceFolderName, getAndroidResourceIdentifier, - getBasePath, -} = require('@react-native/assets-registry/path-support'); +} = require('@react-native/asset-utils'); const invariant = require('invariant'); /** @@ -66,6 +65,11 @@ function getAssetPathInDrawableFolder(asset: PackagerAsset): string { return drawableFolder + '/' + fileName + '.' + asset.type; } +function getBasePath(asset: PackagerAsset): string { + const basePath = asset.httpServerLocation; + return basePath.startsWith('/') ? basePath.slice(1) : basePath; +} + /** * Returns true if the asset can be loaded over the network. * diff --git a/packages/react-native/metro.config.js b/packages/react-native/metro.config.js index dd334817be10..4f1cf7d7449a 100644 --- a/packages/react-native/metro.config.js +++ b/packages/react-native/metro.config.js @@ -26,6 +26,7 @@ const config = { // Make Metro able to resolve required packages that might be imported from /packages/react-native watchFolders: [ path.resolve(__dirname, '../../node_modules'), + path.resolve(__dirname, '../asset-utils'), path.resolve(__dirname, '../assets-registry'), path.resolve(__dirname, '../normalize-color'), path.resolve(__dirname, '../polyfills'), diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 991e42abc39a..970513768eca 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -145,6 +145,7 @@ } }, "dependencies": { + "@react-native/asset-utils": "0.87.0-main", "@react-native/assets-registry": "0.87.0-main", "@react-native/codegen": "0.87.0-main", "@react-native/community-cli-plugin": "0.87.0-main", diff --git a/packages/rn-tester/metro.config.js b/packages/rn-tester/metro.config.js index df8220b22fdb..eb841cfd6628 100644 --- a/packages/rn-tester/metro.config.js +++ b/packages/rn-tester/metro.config.js @@ -24,6 +24,7 @@ const config = { // Make Metro able to resolve required external dependencies watchFolders: [ path.resolve(__dirname, '../../node_modules'), + path.resolve(__dirname, '../asset-utils'), path.resolve(__dirname, '../assets-registry'), path.resolve(__dirname, '../community-cli-plugin'), path.resolve(__dirname, '../dev-middleware'),