Skip to content

Commit

Permalink
Use asset catalog for ios images
Browse files Browse the repository at this point in the history
  • Loading branch information
janicduplessis committed Oct 4, 2022
1 parent e89f296 commit d76a53e
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 13 deletions.
70 changes: 70 additions & 0 deletions packages/cli-plugin-metro/src/commands/bundle/assetCatalogIOS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import path from 'path';
import fs from 'fs-extra';
import assetPathUtils from './assetPathUtils';
import {AssetData} from './buildBundle';

export function cleanAssetCatalog(catalogDir: string): void {
const files = fs
.readdirSync(catalogDir)
.filter((file) => file.endsWith('.imageset'));
for (const file of files) {
fs.removeSync(path.join(catalogDir, file));
}
}

type ImageSet = {
basePath: string;
files: {name: string; src: string; scale: number}[];
};

export function getImageSet(
catalogDir: string,
asset: AssetData,
scales: readonly number[],
): ImageSet {
const fileName = assetPathUtils.getResourceIdentifier(asset);
return {
basePath: path.join(catalogDir, `${fileName}.imageset`),
files: scales.map((scale, idx) => {
const suffix = scale === 1 ? '' : `@${scale}x`;
return {
name: `${fileName + suffix}.${asset.type}`,
scale,
src: asset.files[idx],
};
}),
};
}

export function isCatalogAsset(asset: AssetData): boolean {
return asset.type === 'png' || asset.type === 'jpg' || asset.type === 'jpeg';
}

export function writeImageSet(imageSet: ImageSet): void {
fs.mkdirsSync(imageSet.basePath);

for (const file of imageSet.files) {
const dest = path.join(imageSet.basePath, file.name);
fs.copyFileSync(file.src, dest);
}

fs.writeJSONSync(path.join(imageSet.basePath, 'Contents.json'), {
images: imageSet.files.map((file) => ({
filename: file.name,
idiom: 'universal',
scale: `${file.scale}x`,
})),
info: {
author: 'xcode',
version: 1,
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function getAndroidResourceFolderName(
return androidFolder;
}

function getAndroidResourceIdentifier(asset: PackagerAsset): string {
function getResourceIdentifier(asset: PackagerAsset): string {
const folderPath = getBasePath(asset);
return `${folderPath}/${asset.name}`
.toLowerCase()
Expand All @@ -84,6 +84,6 @@ function getBasePath(asset: PackagerAsset): string {
export default {
getAndroidAssetSuffix,
getAndroidResourceFolderName,
getAndroidResourceIdentifier,
getResourceIdentifier,
getBasePath,
};
7 changes: 6 additions & 1 deletion packages/cli-plugin-metro/src/commands/bundle/buildBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ export async function buildBundleWithConfig(
});

// When we're done saving bundle output and the assets, we're done.
return await saveAssets(outputAssets, args.platform, args.assetsDest);
return await saveAssets(
outputAssets,
args.platform,
args.assetsDest,
args.assetCatalogDest,
);
} finally {
server.end();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import path from 'path';

export interface CommandLineArgs {
assetsDest?: string;
assetCatalogDest?: string;
entryFile: string;
resetCache: boolean;
resetGlobalCache: boolean;
Expand Down Expand Up @@ -102,6 +103,10 @@ export default [
description:
'Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default',
},
{
name: '--asset-catalog-dest [string]',
description: 'Path where to create an iOS Asset Catalog for images',
},
{
name: '--reset-cache',
description: 'Removes cached files',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function getAssetDestPathAndroid(asset: PackagerAsset, scale: number): string {
asset,
scale,
);
const fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
const fileName = assetPathUtils.getResourceIdentifier(asset);
return path.join(androidFolder, `${fileName}.${asset.type}`);
}

Expand Down
56 changes: 47 additions & 9 deletions packages/cli-plugin-metro/src/commands/bundle/saveAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
*
*/

import path from 'path';
import {logger} from '@react-native-community/cli-tools';
import fs from 'fs';

import path from 'path';
import {
cleanAssetCatalog,
getImageSet,
isCatalogAsset,
writeImageSet,
} from './assetCatalogIOS';
import {AssetData} from './buildBundle';
import filterPlatformAssetScales from './filterPlatformAssetScales';
import getAssetDestPathAndroid from './getAssetDestPathAndroid';
import getAssetDestPathIOS from './getAssetDestPathIOS';
import {logger} from '@react-native-community/cli-tools';
import type {AssetData} from './buildBundle';

interface CopiedFiles {
[src: string]: string;
Expand All @@ -23,20 +28,23 @@ function saveAssets(
assets: AssetData[],
platform: string,
assetsDest: string | undefined,
assetCatalogDest: string | undefined,
) {
if (!assetsDest) {
logger.warn('Assets destination folder is not set, skipping...');
return Promise.resolve();
return;
}

const filesToCopy: CopiedFiles = Object.create(null); // Map src -> dest

const getAssetDestPath =
platform === 'android' ? getAssetDestPathAndroid : getAssetDestPathIOS;

const filesToCopy: CopiedFiles = Object.create(null); // Map src -> dest
assets.forEach((asset) => {
const addAssetToCopy = (asset: AssetData) => {
const validScales = new Set(
filterPlatformAssetScales(platform, asset.scales),
);

asset.scales.forEach((scale, idx) => {
if (!validScales.has(scale)) {
return;
Expand All @@ -45,7 +53,37 @@ function saveAssets(
const dest = path.join(assetsDest, getAssetDestPath(asset, scale));
filesToCopy[src] = dest;
});
});
};

if (platform === 'ios' && assetCatalogDest != null) {
// Use iOS Asset Catalog for images. This will allow Apple app thinning to
// remove unused scales from the optimized bundle.
const catalogDir = path.join(assetCatalogDest, 'RNAssets.xcassets');
if (!fs.existsSync(catalogDir)) {
logger.error(
`Could not find asset catalog 'RNAssets.xcassets' in ${assetCatalogDest}. Make sure to create it if it does not exist.`,
);
return;
}

logger.info('Adding images to asset catalog', catalogDir);
cleanAssetCatalog(catalogDir);
for (const asset of assets) {
if (isCatalogAsset(asset)) {
const imageSet = getImageSet(
catalogDir,
asset,
filterPlatformAssetScales(platform, asset.scales),
);
writeImageSet(imageSet);
} else {
addAssetToCopy(asset);
}
}
logger.info('Done adding images to asset catalog');
} else {
assets.forEach(addAssetToCopy);
}

return copyAll(filesToCopy);
}
Expand All @@ -57,7 +95,7 @@ function copyAll(filesToCopy: CopiedFiles) {
}

logger.info(`Copying ${queue.length} asset files`);
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
const copyNext = (error?: NodeJS.ErrnoException) => {
if (error) {
reject(error);
Expand Down

0 comments on commit d76a53e

Please # to comment.