Skip to content

Commit d806b04

Browse files
bartekkrokdannyhw
andauthored
feat: add maxInlineSize option for size-based asset inlining (#1378)
Co-authored-by: Daniel Williams <me@dannyhw.com>
1 parent 13e7ed5 commit d806b04

20 files changed

Lines changed: 538 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@callstack/repack": minor
3+
---
4+
5+
Add `maxInlineSize` option to assets loader for size-based asset inlining. Use it together with `inline: true` — assets whose largest variant is within the threshold are inlined as base64 URIs; larger assets are extracted as separate files.

apps/tester-federation-v2/configs/rspack.host-app.mts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export default Repack.defineRspackConfig((env) => {
2727
},
2828
type: 'javascript/auto',
2929
},
30-
...Repack.getAssetTransformRules(),
30+
...Repack.getAssetTransformRules({
31+
inline: true,
32+
maxInlineSize: 90 * 1024,
33+
}),
3134
],
3235
},
3336
plugins: [
87.3 KB
Loading
119 KB
Loading
58.5 KB
Loading

apps/tester-federation-v2/src/host/navigation/MainNavigator.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { StyleSheet } from 'react-native';
66

77
import DetailScreen from '../screens/DetailScreen';
88
import HomeScreen from '../screens/HomeScreen';
9+
import HostAssetsScreen from '../screens/HostAssetsScreen';
910
import MiniAppScreen from '../screens/MiniAppScreen';
1011

1112
export type MainStackParamList = {
1213
Home: undefined;
1314
Detail: undefined;
15+
HostAssets: undefined;
1416
MiniApp: undefined;
1517
};
1618

@@ -34,6 +36,19 @@ const MainNavigator = () => {
3436
>
3537
<Main.Screen name="Home" component={HomeScreen} />
3638
<Main.Screen name="Detail" component={DetailScreen} />
39+
<Main.Screen
40+
name="HostAssets"
41+
component={HostAssetsScreen}
42+
options={{
43+
title: 'Host Assets',
44+
headerTitle: 'Host Assets',
45+
headerLargeTitleEnabled: true,
46+
headerStyle: styles.header,
47+
headerLargeStyle: styles.hostAssetsLargeHeader,
48+
headerTitleStyle: styles.headerTitle,
49+
headerLargeTitleStyle: styles.hostAssetsLargeTitle,
50+
}}
51+
/>
3752
<Main.Screen
3853
name="MiniApp"
3954
component={MiniAppScreen}
@@ -50,6 +65,12 @@ const styles = StyleSheet.create({
5065
headerTitle: {
5166
color: '#FFFFFF', // White text
5267
},
68+
hostAssetsLargeHeader: {
69+
backgroundColor: 'transparent',
70+
},
71+
hostAssetsLargeTitle: {
72+
color: '#2C3E50',
73+
},
5374
});
5475

5576
export default MainNavigator;

apps/tester-federation-v2/src/host/screens/HomeScreen.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ const HomeScreen = () => {
1818
>
1919
<Text style={styles.buttonText}>See details</Text>
2020
</TouchableOpacity>
21+
<TouchableOpacity
22+
style={[styles.button, styles.secondaryButton]}
23+
onPress={() => navigation.navigate('HostAssets')}
24+
>
25+
<Text style={styles.secondaryButtonText}>Host assets</Text>
26+
</TouchableOpacity>
2127
</View>
2228
<View style={styles.miniAppSection}>
2329
<Text style={styles.sectionTitle}>Mini Apps</Text>
@@ -84,11 +90,22 @@ const styles = StyleSheet.create({
8490
borderRadius: 10,
8591
alignItems: 'center',
8692
},
93+
secondaryButton: {
94+
marginTop: 12,
95+
backgroundColor: '#FFFFFF',
96+
borderColor: '#3498DB',
97+
borderWidth: 1,
98+
},
8799
buttonText: {
88100
color: 'white',
89101
fontSize: 16,
90102
fontWeight: '600',
91103
},
104+
secondaryButtonText: {
105+
color: '#3498DB',
106+
fontSize: 16,
107+
fontWeight: '600',
108+
},
92109
miniAppSection: {
93110
backgroundColor: 'white',
94111
padding: 20,
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import {
2+
FlatList,
3+
Image,
4+
type ImageRequireSource,
5+
StyleSheet,
6+
Text,
7+
View,
8+
} from 'react-native';
9+
10+
const assets = [
11+
{
12+
source: require('../assets/pic_1.jpg'),
13+
status: 'Inlined',
14+
size: '89,400 bytes',
15+
},
16+
{
17+
source: require('../assets/pic_2.jpg'),
18+
status: 'Emitted',
19+
size: '121,569 bytes',
20+
},
21+
{
22+
source: require('../assets/pic_3.jpg'),
23+
status: 'Inlined',
24+
size: '59,862 bytes',
25+
},
26+
];
27+
28+
const data = assets.map((asset, index) => ({
29+
title: `Host asset ${index + 1}`,
30+
...asset,
31+
}));
32+
33+
const AssetRow = ({
34+
title,
35+
source,
36+
status,
37+
size,
38+
}: {
39+
title: string;
40+
source: ImageRequireSource;
41+
status: string;
42+
size: string;
43+
}) => (
44+
<View style={styles.row}>
45+
<Image source={source} style={styles.image} />
46+
<View style={styles.titleContainer}>
47+
<View style={styles.titleRow}>
48+
<Text style={styles.title}>{title}</Text>
49+
<View
50+
style={[
51+
styles.badge,
52+
status === 'Inlined' ? styles.inlineBadge : styles.emittedBadge,
53+
]}
54+
>
55+
<Text
56+
style={[
57+
styles.badgeText,
58+
status === 'Inlined'
59+
? styles.inlineBadgeText
60+
: styles.emittedBadgeText,
61+
]}
62+
>
63+
{status}
64+
</Text>
65+
</View>
66+
</View>
67+
<Text style={styles.size}>{size}</Text>
68+
<Text style={styles.subtitle}>
69+
Loaded from the host app bundle to test inline asset size limits
70+
</Text>
71+
</View>
72+
</View>
73+
);
74+
75+
const HostAssetsScreen = () => {
76+
return (
77+
<FlatList
78+
contentInsetAdjustmentBehavior="automatic"
79+
contentContainerStyle={styles.contentContainer}
80+
data={data}
81+
keyExtractor={(item) => item.title}
82+
renderItem={({ item }) => (
83+
<AssetRow
84+
title={item.title}
85+
source={item.source}
86+
status={item.status}
87+
size={item.size}
88+
/>
89+
)}
90+
style={styles.container}
91+
/>
92+
);
93+
};
94+
95+
const styles = StyleSheet.create({
96+
container: {
97+
flex: 1,
98+
backgroundColor: '#F5F9FF',
99+
},
100+
contentContainer: {
101+
paddingVertical: 20,
102+
},
103+
row: {
104+
marginBottom: 20,
105+
marginHorizontal: 15,
106+
backgroundColor: 'white',
107+
borderRadius: 10,
108+
overflow: 'hidden',
109+
shadowColor: '#2C3E50',
110+
shadowOffset: { width: 0, height: 2 },
111+
shadowOpacity: 0.1,
112+
shadowRadius: 4,
113+
elevation: 3,
114+
},
115+
image: {
116+
width: '100%',
117+
height: 250,
118+
resizeMode: 'cover',
119+
},
120+
titleContainer: {
121+
padding: 15,
122+
},
123+
titleRow: {
124+
alignItems: 'center',
125+
flexDirection: 'row',
126+
gap: 10,
127+
marginBottom: 5,
128+
},
129+
title: {
130+
flex: 1,
131+
fontSize: 18,
132+
fontWeight: '600',
133+
color: '#2C3E50',
134+
},
135+
badge: {
136+
borderRadius: 4,
137+
paddingHorizontal: 8,
138+
paddingVertical: 4,
139+
},
140+
inlineBadge: {
141+
backgroundColor: '#DFF5E7',
142+
},
143+
emittedBadge: {
144+
backgroundColor: '#FFF1D6',
145+
},
146+
badgeText: {
147+
fontSize: 12,
148+
fontWeight: '700',
149+
},
150+
inlineBadgeText: {
151+
color: '#1E7F45',
152+
},
153+
emittedBadgeText: {
154+
color: '#9A5A00',
155+
},
156+
size: {
157+
color: '#7F8C8D',
158+
fontSize: 13,
159+
marginBottom: 5,
160+
},
161+
subtitle: {
162+
fontSize: 14,
163+
color: '#3498DB',
164+
fontWeight: '400',
165+
},
166+
});
167+
168+
export default HostAssetsScreen;

packages/repack/src/loaders/assetsLoader/assetsLoader.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,15 @@ export default async function repackAssetsLoader(
222222
})
223223
);
224224

225+
const largestVariantSize = Math.max(
226+
...assets.map((asset) => asset.data.length)
227+
);
228+
const shouldInlineAsset =
229+
!!options.inline &&
230+
(!options.maxInlineSize || largestVariantSize <= options.maxInlineSize);
231+
225232
let result: string;
226-
if (options.inline) {
233+
if (shouldInlineAsset) {
227234
logger.debug(`Inlining assets for request ${resourcePath}`);
228235
result = inlineAssets({ assets, resourcePath });
229236
} else {

packages/repack/src/loaders/assetsLoader/options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface AssetLoaderOptions {
1818
scalableAssetExtensions?: string[];
1919
scalableAssetResolutions?: string[];
2020
inline?: boolean;
21+
maxInlineSize?: number;
2122
publicPath?: string;
2223
remote?: AssetLoaderRemoteOptions;
2324
}
@@ -39,6 +40,7 @@ export const optionsSchema: Schema = {
3940
type: 'array',
4041
},
4142
inline: { type: 'boolean' },
43+
maxInlineSize: { type: 'number' },
4244
publicPath: { type: 'string' },
4345
remote: {
4446
type: 'object',

0 commit comments

Comments
 (0)