@expo/metro-config
Version:
A Metro config for running React Native projects with the Metro bundler
90 lines • 4.77 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUniversalAssetData = getUniversalAssetData;
exports.default = getAssets;
const Assets_1 = require("metro/src/Assets");
const js_js_1 = require("metro/src/DeltaBundler/Serializers/helpers/js.js");
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const debug = require('debug')('expo:metro-config:assets');
function getMD5ForData(data) {
if (data.length === 1)
return data[0];
const hash = node_crypto_1.default.createHash('md5');
hash.update(data.join(''));
return hash.digest('hex');
}
function getMD5ForFilePathAsync(path) {
return new Promise((resolve, reject) => {
const output = node_crypto_1.default.createHash('md5');
const input = node_fs_1.default.createReadStream(path);
input.on('error', (err) => reject(err));
output.on('error', (err) => reject(err));
output.once('readable', () => resolve(output.read().toString('hex')));
input.pipe(output);
});
}
function isHashedAssetData(asset) {
if ('fileHashes' in asset && Array.isArray(asset.fileHashes)) {
return true;
}
return false;
}
async function ensureOtaAssetHashesAsync(asset) {
// Legacy cases where people have the `expo-asset/tools/hashAssetFiles` set still.
if (isHashedAssetData(asset)) {
debug('fileHashes already added, skipping injection for: ' + asset.name);
return asset;
}
const hashes = await Promise.all(asset.files.map(getMD5ForFilePathAsync));
// New version where we run the asset plugin every time.
asset.fileHashes = hashes;
// Convert the `../` segments of the server URL to `_` to support monorepos.
// This same transformation takes place in `AssetSourceResolver.web` (expo-assets, expo-image) and `persistMetroAssets` of Expo CLI,
// this originally came from the Metro opinion https://github.com/react-native-community/cli/blob/2204d357379e2067cebe2791e90388f7e97fc5f5/packages/cli-plugin-metro/src/commands/bundle/getAssetDestPathIOS.ts#L19C5-L19C10
if (asset.httpServerLocation.includes('?export_path=')) {
// @ts-expect-error: marked as read-only
asset.httpServerLocation = asset.httpServerLocation
.match(/\?export_path=(.*)/)[1]
.replace(/\.\.\//g, '_');
}
// URL encode asset paths defined as `?export_path` or `?unstable_path` query parameters.
// Decoding should be done automatically when parsing the URL through Node or the browser.
const assetPathQueryParameter = asset.httpServerLocation.match(/\?(export_path|unstable_path)=(.*)/);
if (assetPathQueryParameter && assetPathQueryParameter[2]) {
const assetPath = assetPathQueryParameter[2];
// @ts-expect-error: marked as read-only
asset.httpServerLocation = asset.httpServerLocation.replace(assetPath, encodeURIComponent(assetPath));
}
return asset;
}
async function getUniversalAssetData(assetPath, localPath, assetDataPlugins, platform, publicPath) {
const metroAssetData = await (0, Assets_1.getAssetData)(assetPath, localPath, assetDataPlugins, platform, publicPath);
const data = await ensureOtaAssetHashesAsync(metroAssetData);
// NOTE(EvanBacon): This is where we modify the asset to include a hash in the name for web cache invalidation.
if (platform === 'web' && publicPath.includes('?export_path=')) {
// `local-image.[contenthash]`. Using `.` but this won't work if we ever apply to Android because Android res files cannot contain `.`.
// TODO: Prevent one multi-res image from updating the hash in all images.
// @ts-expect-error: name is typed as readonly.
data.name = `${data.name}.${getMD5ForData(data.fileHashes)}`;
}
return data;
}
async function getAssets(dependencies, options) {
const promises = [];
const { processModuleFilter } = options;
for (const module of dependencies.values()) {
if ((0, js_js_1.isJsModule)(module) &&
processModuleFilter(module) &&
(0, js_js_1.getJsOutput)(module).type === 'js/module/asset' &&
node_path_1.default.relative(options.projectRoot, module.path) !== 'package.json') {
promises.push(getUniversalAssetData(module.path, node_path_1.default.relative(options.projectRoot, module.path), options.assetPlugins, options.platform, options.publicPath));
}
}
return await Promise.all(promises);
}
//# sourceMappingURL=getAssets.js.map