Skip to content

Commit

Permalink
Fix selection of assets when assetExts include a multipart extension
Browse files Browse the repository at this point in the history
Summary:
Resolves #937.

Previously, the matching of `resolver.assetExts` in the resolve and transform steps was based on `path.extname()`, which would only consider the final segment in a path split by a `'.'`. This meant the inability to configure and match multipart extensions in `assetExts`, such as `'.asset.json'`. The new implementation iterates over all possible file extensions.

Validated against:
- Internal test app.
- Sample repo with relevant Metro config.

Changelog: **[Fix]** `resolver.assetExts` will now match asset files for extension values that include a dot (`.`)

Reviewed By: motiz88

Differential Revision: D43737453

fbshipit-source-id: 150c3aaf3bd58c24cd1670cc1a46e8531c24acf7
  • Loading branch information
huntie authored and facebook-github-bot committed Mar 20, 2023
1 parent 439f0d0 commit 6d65a32
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 1 deletion.
86 changes: 86 additions & 0 deletions packages/metro-resolver/src/__tests__/assets-test.js
Original file line number Diff line number Diff line change
@@ -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'],
});
});
});
14 changes: 13 additions & 1 deletion packages/metro-resolver/src/utils/isAssetFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,17 @@ export default function isAssetFile(
filePath: string,
assetExts: $ReadOnlySet<string>,
): 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;
}

0 comments on commit 6d65a32

Please sign in to comment.