Skip to content

Commit 2028515

Browse files
authored
Merge pull request Expensify#68727 from callstack-internal/perf/lazy-load-heic-to
Lazy load HEIC conversion library on web
2 parents 4a788e7 + f1ebff1 commit 2028515

2 files changed

Lines changed: 33 additions & 11 deletions

File tree

config/webpack/webpack.common.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment):
342342
name: 'lottiePlayer',
343343
chunks: 'all',
344344
},
345+
// heic-to library is used sparsely and we want to load it as a separate chunk
346+
// to reduce the potential bundled size of the initial chunk
347+
heicTo: {
348+
test: /[\\/]node_modules[\\/](heic-to)[\\/]/,
349+
name: 'heicTo',
350+
chunks: 'all',
351+
},
345352
// Extract all 3rd party dependencies (~75% of App) to separate js file
346353
// This gives a more efficient caching - 3rd party deps don't change as often as main source
347354
// When dependencies don't change webpack would produce the same js file (and content hash)

src/libs/fileDownload/heicConverter/index.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import {heicTo, isHeic} from 'heic-to';
21
import type {HeicConverterFunction} from './types';
32

3+
type HeicConverter = {
4+
heicTo: (options: {blob: Blob; type: string}) => Promise<Blob>;
5+
isHeic: (file: File) => Promise<boolean>;
6+
};
7+
8+
const getHeicConverter = () => {
9+
// Use the CSP variant to ensure the library is loaded in a secure context. See https://github.com/hoppergee/heic-to?tab=readme-ov-file#cotent-security-policy
10+
// Use webpackMode: "eager" to ensure the library is loaded immediately without evaluating the code. See https://github.com/Expensify/App/pull/68727#issuecomment-3227196372
11+
// @ts-expect-error - heic-to/csp is not correctly typed but exists
12+
return import(/* webpackMode: "eager" */ 'heic-to/csp').then(({heicTo, isHeic}: HeicConverter) => ({heicTo, isHeic}));
13+
};
14+
415
/**
516
* Web implementation for converting HEIC/HEIF images to JPEG
617
* @param file - The file to check and potentially convert
@@ -23,12 +34,18 @@ const convertHeicImage: HeicConverterFunction = (file, {onSuccess = () => {}, on
2334
return;
2435
}
2536

26-
fetch(file.uri)
27-
.then((response) => response.blob())
28-
.then((blob) => {
29-
const fileFromBlob = new File([blob], file.name ?? 'temp-file', {
30-
type: blob.type,
31-
});
37+
// Start loading the conversion library in parallel with fetching the file
38+
const libraryPromise = getHeicConverter().catch((importError) => {
39+
console.error('Error loading heic-to library:', importError);
40+
// Re-throw a normalized error so the outer catch can handle it uniformly
41+
throw new Error('HEIC conversion library unavailable');
42+
});
43+
44+
const fetchBlobPromise = fetch(file.uri).then((response) => response.blob());
45+
46+
Promise.all([libraryPromise, fetchBlobPromise])
47+
.then(([{heicTo, isHeic}, blob]) => {
48+
const fileFromBlob = new File([blob], file.name ?? 'temp-file', {type: blob.type});
3249

3350
return isHeic(fileFromBlob).then((isHEIC) => {
3451
if (isHEIC || needsConversion) {
@@ -45,19 +62,17 @@ const convertHeicImage: HeicConverterFunction = (file, {onSuccess = () => {}, on
4562
.catch((err) => {
4663
console.error('Error converting image format to JPEG:', err);
4764
onError(err, file);
48-
})
49-
.finally(() => {
50-
onFinish();
5165
});
5266
}
5367

5468
onSuccess(file);
55-
onFinish();
5669
});
5770
})
5871
.catch((err) => {
5972
console.error('Error processing the file:', err);
6073
onError(err, file);
74+
})
75+
.finally(() => {
6176
onFinish();
6277
});
6378
};

0 commit comments

Comments
 (0)