From 057925b611a4c70eb66dc30de1bcda5ab309b7ad Mon Sep 17 00:00:00 2001 From: Johnchi Date: Sun, 28 Jun 2026 19:08:21 +0100 Subject: [PATCH] refactor: optimize font loading process with critical and secondary font handling --- App.tsx | 25 ++++++++++++++++----- src/services/fontService.ts | 44 +++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/App.tsx b/App.tsx index 5c049079..275edc1a 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,4 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import * as Font from 'expo-font'; import * as Notifications from 'expo-notifications'; import * as SplashScreen from 'expo-splash-screen'; import { StatusBar } from 'expo-status-bar'; @@ -28,6 +27,11 @@ import { subscribeToCacheStatus, } from './src/services/api'; import { warmCriticalCaches } from './src/services/cacheWarming'; +import { + CRITICAL_FONTS, + fontService, + SECONDARY_FONTS, +} from './src/services/fontService'; import { crashReportingService } from './src/services/crashReporting'; import { featureCapabilities } from './src/services/featureCapabilities'; import { inAppReviewService } from './src/services/inAppReview'; @@ -197,11 +201,10 @@ const App = () => { useEffect(() => { async function prepareApp() { try { - // 1. Load fonts - await Font.loadAsync({ - 'Inter-Regular': require('./assets/fonts/Inter-Regular.ttf'), - 'Inter-Bold': require('./assets/fonts/Inter-Bold.ttf'), - }); + // 1. Load critical fonts (used on first screen) synchronously before splash hides + const fontStart = Date.now(); + await fontService.loadFonts(CRITICAL_FONTS); + console.log(`[App] Critical fonts loaded in ${Date.now() - fontStart}ms`); // 2. Version-based cache invalidation: clear stale caches on app/data version bump const appVersion = require('./package.json').version as string; @@ -220,6 +223,16 @@ const App = () => { prepareApp(); }, []); + useEffect(() => { + if (!appIsReady) return; + + InteractionManager.runAfterInteractions(async () => { + const start = Date.now(); + await fontService.loadFonts(SECONDARY_FONTS); + console.log(`[App] Secondary fonts loaded in ${Date.now() - start}ms`); + }); + }, [appIsReady]); + useEffect(() => { // ===== CRITICAL PATH — runs immediately ===== // These tasks are essential for core app functionality and must complete diff --git a/src/services/fontService.ts b/src/services/fontService.ts index a6b73a0a..e3c0355a 100644 --- a/src/services/fontService.ts +++ b/src/services/fontService.ts @@ -1,6 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { Asset } from 'expo-asset'; -import { loadAsync } from 'expo-font'; +import * as Font from 'expo-font'; import { Platform } from 'react-native'; // Font metadata interface @@ -151,7 +151,7 @@ class FontService { try { // Load the font - await loadAsync({ [name]: source }); + await Font.loadAsync({ [name]: source }); this.loadedFonts.add(name); // Update metadata @@ -256,6 +256,20 @@ class FontService { }; } + // Load an array of font entries with timing tracking + async loadFonts(fonts: { name: string; source: string | number }[]): Promise { + const start = Date.now(); + const entries = fonts.map(f => ({ [f.name]: f.source })); + const flat = Object.assign({}, ...entries); + await Font.loadAsync(flat); + entries.forEach(e => { + const name = Object.keys(e)[0]; + this.loadedFonts.add(name); + }); + const elapsed = Date.now() - start; + console.log(`[FontService] Loaded ${fonts.length} font(s) in ${elapsed}ms`); + } + // Preload critical fonts async preloadCriticalFonts(fonts: { name: string; source: string | number }[]): Promise<{ loaded: string[]; @@ -294,6 +308,32 @@ class FontService { // Singleton instance export const fontService = new FontService(); +// Font entry for loading +export interface FontEntry { + name: string; + source: any; +} + +// All available font variants mapped to asset sources +export const FONTS = { + 'Inter-Regular': require('../../assets/fonts/Inter-Regular.ttf'), + 'Inter-Bold': require('../../assets/fonts/Inter-Bold.ttf'), + 'Inter-Medium': require('../../assets/fonts/Inter-Medium.ttf'), + 'Inter-SemiBold': require('../../assets/fonts/Inter-SemiBold.ttf'), +} as const; + +// Fonts required for the initial screen render (loaded before splash hide) +export const CRITICAL_FONTS: FontEntry[] = [ + { name: 'Inter-Regular', source: FONTS['Inter-Regular'] }, + { name: 'Inter-Medium', source: FONTS['Inter-Medium'] }, + { name: 'Inter-Bold', source: FONTS['Inter-Bold'] }, +]; + +// Fonts loaded lazily after initial render completes +export const SECONDARY_FONTS: FontEntry[] = [ + { name: 'Inter-SemiBold', source: FONTS['Inter-SemiBold'] }, +]; + // Utility functions for font optimization export const fontOptimization = { // Calculate font subset based on character usage