diff --git a/packages/metro-resolver/src/__tests__/assets-test.js b/packages/metro-resolver/src/__tests__/assets-test.js new file mode 100644 index 0000000000..91a0515129 --- /dev/null +++ b/packages/metro-resolver/src/__tests__/assets-test.js @@ -0,0 +1,86 @@ +/** + * 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 + * @oncall react_native + */ + +'use strict'; + +import path from 'path'; +import Resolver from '../index'; +import {createResolutionContext} from './utils'; + +describe('asset resolutions', () => { + const baseContext = { + ...createResolutionContext({ + '/root/project/index.js': '', + '/root/project/src/data.json': '', + '/root/project/assets/example.asset.json': '', + '/root/project/assets/icon.png': '', + '/root/project/assets/icon@2x.png': '', + }), + originModulePath: '/root/project/index.js', + }; + const assetResolutions = ['1', '2']; + const resolveAsset = ( + dirPath: string, + assetName: string, + extension: string, + ) => { + const basePath = dirPath + path.sep + assetName; + let assets = [ + basePath + extension, + ...assetResolutions.map( + resolution => basePath + '@' + resolution + 'x' + extension, + ), + ]; + + assets = assets.filter(candidate => baseContext.doesFileExist(candidate)); + + return assets.length ? assets : null; + }; + + test('should resolve a path as an asset when matched against `assetExts`', () => { + const context = { + ...baseContext, + assetExts: new Set(['png']), + resolveAsset, + }; + + expect(Resolver.resolve(context, './assets/icon.png', null)).toEqual({ + type: 'assetFiles', + filePaths: [ + '/root/project/assets/icon.png', + '/root/project/assets/icon@2x.png', + ], + }); + }); + + test('should resolve a path as an asset when matched against `assetExts` (overlap with `sourceExts`)', () => { + const context = { + ...baseContext, + assetExts: new Set(['asset.json']), + resolveAsset, + sourceExts: ['js', 'json'], + }; + + // Source file matching `sourceExts` + expect(Resolver.resolve(context, './src/data.json', null)).toEqual({ + type: 'sourceFile', + filePath: '/root/project/src/data.json', + }); + + // Asset file matching more specific asset ext + expect( + Resolver.resolve(context, './assets/example.asset.json', null), + ).toEqual({ + type: 'assetFiles', + filePaths: ['/root/project/assets/example.asset.json'], + }); + }); +}); diff --git a/packages/metro-resolver/src/utils/isAssetFile.js b/packages/metro-resolver/src/utils/isAssetFile.js index 725d724c1e..1380c63eb6 100644 --- a/packages/metro-resolver/src/utils/isAssetFile.js +++ b/packages/metro-resolver/src/utils/isAssetFile.js @@ -19,5 +19,17 @@ export default function isAssetFile( filePath: string, assetExts: $ReadOnlySet, ): boolean { - return assetExts.has(path.extname(filePath).slice(1)); + const baseName = path.basename(filePath); + + for (let i = baseName.length - 1; i >= 0; i--) { + if (baseName[i] === '.') { + const ext = baseName.slice(i + 1); + + if (assetExts.has(ext)) { + return true; + } + } + } + + return false; }