1import { ExpoAppManifest } from '@expo/config'; 2import { ModPlatform } from '@expo/config-plugins'; 3import minimatch from 'minimatch'; 4import path from 'path'; 5 6import * as Log from '../log'; 7import { resolveGoogleServicesFile } from '../start/server/middleware/resolveAssets'; 8import { uniqBy } from '../utils/array'; 9import { BundleOutput } from './fork-bundleAsync'; 10import { Asset, saveAssetsAsync } from './saveAssets'; 11 12const debug = require('debug')('expo:export:exportAssets') as typeof console.log; 13 14/** 15 * Resolves the assetBundlePatterns from the manifest and returns a list of assets to bundle. 16 * 17 * @modifies {exp} 18 */ 19export async function resolveAssetBundlePatternsAsync( 20 projectRoot: string, 21 exp: Pick<ExpoAppManifest, 'bundledAssets' | 'assetBundlePatterns'>, 22 assets: Asset[] 23) { 24 if (!exp.assetBundlePatterns?.length || !assets.length) { 25 delete exp.assetBundlePatterns; 26 return exp; 27 } 28 // Convert asset patterns to a list of asset strings that match them. 29 // Assets strings are formatted as `asset_<hash>.<type>` and represent 30 // the name that the file will have in the app bundle. The `asset_` prefix is 31 // needed because android doesn't support assets that start with numbers. 32 33 const fullPatterns: string[] = exp.assetBundlePatterns.map((p: string) => 34 path.join(projectRoot, p) 35 ); 36 37 logPatterns(fullPatterns); 38 39 const allBundledAssets = assets 40 .map((asset) => { 41 const shouldBundle = shouldBundleAsset(asset, fullPatterns); 42 if (shouldBundle) { 43 debug(`${shouldBundle ? 'Include' : 'Exclude'} asset ${asset.files?.[0]}`); 44 return asset.fileHashes.map( 45 (hash) => 'asset_' + hash + ('type' in asset && asset.type ? '.' + asset.type : '') 46 ); 47 } 48 return []; 49 }) 50 .flat(); 51 52 // The assets returned by the RN packager has duplicates so make sure we 53 // only bundle each once. 54 exp.bundledAssets = [...new Set(allBundledAssets)]; 55 delete exp.assetBundlePatterns; 56 57 return exp; 58} 59 60function logPatterns(patterns: string[]) { 61 // Only log the patterns in debug mode, if they aren't already defined in the app.json, then all files will be targeted. 62 Log.log('\nProcessing asset bundle patterns:'); 63 patterns.forEach((p) => Log.log('- ' + p)); 64} 65 66function shouldBundleAsset(asset: Asset, patterns: string[]) { 67 const file = asset.files?.[0]; 68 return !!( 69 '__packager_asset' in asset && 70 asset.__packager_asset && 71 file && 72 patterns.some((pattern) => minimatch(file, pattern)) 73 ); 74} 75 76export async function exportAssetsAsync( 77 projectRoot: string, 78 { 79 exp, 80 outputDir, 81 bundles, 82 }: { 83 exp: ExpoAppManifest; 84 bundles: Partial<Record<ModPlatform, BundleOutput>>; 85 outputDir: string; 86 } 87) { 88 const assets: Asset[] = uniqBy( 89 Object.values(bundles).flatMap((bundle) => bundle!.assets), 90 (asset) => asset.hash 91 ); 92 93 if (assets[0]?.fileHashes) { 94 Log.log('Saving assets'); 95 await saveAssetsAsync(projectRoot, { assets, outputDir }); 96 } 97 98 // Add google services file if it exists 99 await resolveGoogleServicesFile(projectRoot, exp); 100 101 // Updates the manifest to reflect additional asset bundling + configs 102 await resolveAssetBundlePatternsAsync(projectRoot, exp, assets); 103 104 return { exp, assets }; 105} 106