Skip to content

Commit 5bbf8dd

Browse files
fix: new approach to load fonts for all platforms
1 parent 0320212 commit 5bbf8dd

File tree

6 files changed

+71
-67
lines changed

6 files changed

+71
-67
lines changed

boilerplate/app.config.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
import { ExpoConfig, ConfigContext } from "@expo/config"
2+
import fs from "fs"
3+
import path from "path"
4+
5+
/**
6+
* Automatically discover all .ttf fonts under app/assets/fonts
7+
* so we never have to hardcode them.
8+
*/
9+
function getFontFiles(): string[] {
10+
const fontsDir = path.resolve(__dirname, "app/assets/fonts")
11+
if (!fs.existsSync(fontsDir)) return []
12+
13+
return fs
14+
.readdirSync(fontsDir)
15+
.filter((file) => file.endsWith(".ttf"))
16+
.map((file) => `./app/assets/fonts/${file}`)
17+
}
218

319
/**
420
* Use ts-node here so we can use TypeScript for our Config Plugins
@@ -14,9 +30,10 @@ require("ts-node/register")
1430
*/
1531
module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
1632
const existingPlugins = config.plugins ?? []
17-
33+
const fonts = getFontFiles()
1834
return {
1935
...config,
36+
2037
ios: {
2138
...config.ios,
2239
// This privacyManifests is to get you started.
@@ -34,6 +51,15 @@ module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
3451
],
3552
},
3653
},
37-
plugins: [...existingPlugins, require("./plugins/withSplashScreen").withSplashScreen],
54+
plugins: [
55+
...existingPlugins,
56+
[
57+
"expo-font",
58+
{
59+
fonts,
60+
},
61+
],
62+
require("./plugins/withSplashScreen").withSplashScreen,
63+
],
3864
}
3965
}

boilerplate/app.json

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,6 @@
3535
},
3636
"plugins": [
3737
"expo-localization",
38-
[
39-
"expo-font",
40-
{
41-
"fonts": [
42-
"./assets/fonts/SpaceGrotesk-300Light.ttf",
43-
"./assets/fonts/SpaceGrotesk-400Regular.ttf",
44-
"./assets/fonts/SpaceGrotesk-500Medium.ttf",
45-
"./assets/fonts/SpaceGrotesk-600SemiBold.ttf",
46-
"./assets/fonts/SpaceGrotesk-700Bold.ttf"
47-
]
48-
}
49-
],
5038
[
5139
"expo-splash-screen",
5240
{

boilerplate/app/app.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,15 @@ if (__DEV__) {
1919
import "./utils/gestureHandler"
2020

2121
import { useEffect, useState } from "react"
22-
import { Platform } from "react-native"
23-
import { useFonts } from "expo-font"
2422
import * as Linking from "expo-linking"
2523
import { KeyboardProvider } from "react-native-keyboard-controller"
2624
import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context"
27-
2825
import { AuthProvider } from "./context/AuthContext" // @demo remove-current-line
2926
import { initI18n } from "./i18n"
3027
import { AppNavigator } from "./navigators/AppNavigator"
3128
import { useNavigationPersistence } from "./navigators/navigationUtilities"
3229
import { ThemeProvider } from "./theme/context"
33-
import { customFontsToLoadWebOnly } from "./theme/typography"
30+
import { useCustomFonts } from "./theme/typography"
3431
import { loadDateFnsLocale } from "./utils/formatDate"
3532
import * as storage from "./utils/storage"
3633

@@ -69,11 +66,7 @@ export function App() {
6966
isRestored: isNavigationStateRestored,
7067
} = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY)
7168

72-
// We load fonts dynamically for web only, the rest are handled by
73-
// the expo-font config plugin in `app.json`. If not using web,
74-
// you can delete this permissive check along with associated
75-
// code in `typography'.
76-
const [areFontsLoadedWebOnly, fontLoadErrorWebOnly] = useFonts(customFontsToLoadWebOnly)
69+
const [areFontsLoaded, fontLoadError] = useCustomFonts()
7770
const [isI18nInitialized, setIsI18nInitialized] = useState(false)
7871

7972
useEffect(() => {
@@ -88,11 +81,7 @@ export function App() {
8881
// In iOS: application:didFinishLaunchingWithOptions:
8982
// In Android: https://stackoverflow.com/a/45838109/204044
9083
// You can replace with your own loading component if you wish.
91-
if (
92-
!isNavigationStateRestored ||
93-
!isI18nInitialized ||
94-
(!areFontsLoadedWebOnly && !fontLoadErrorWebOnly && Platform.OS === "web")
95-
) {
84+
if (!isNavigationStateRestored || !isI18nInitialized || !areFontsLoaded || fontLoadError) {
9685
return null
9786
}
9887

boilerplate/app/theme/typography.ts

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,63 @@
22
// markdown file and add links from here
33

44
import { Platform } from "react-native"
5-
import type { FontSource } from "expo-font"
6-
import {
7-
SpaceGrotesk_300Light as spaceGroteskLight,
8-
SpaceGrotesk_400Regular as spaceGroteskRegular,
9-
SpaceGrotesk_500Medium as spaceGroteskMedium,
10-
SpaceGrotesk_600SemiBold as spaceGroteskSemiBold,
11-
SpaceGrotesk_700Bold as spaceGroteskBold,
12-
} from "@expo-google-fonts/space-grotesk"
5+
import { FontSource, useFonts } from "expo-font"
136

14-
export const customFontsToLoadWebOnly =
15-
Platform.OS === "web"
16-
? {
17-
spaceGroteskLight,
18-
spaceGroteskRegular,
19-
spaceGroteskMedium,
20-
spaceGroteskSemiBold,
21-
spaceGroteskBold,
22-
}
23-
: ({} as Record<string, FontSource>)
7+
export const FONT_FILES: Record<string, string> = {
8+
SpaceGrotesk_300Light: require("../../assets/fonts/SpaceGrotesk-300Light.ttf"),
9+
SpaceGrotesk_400Regular: require("../../assets/fonts/SpaceGrotesk-400Regular.ttf"),
10+
SpaceGrotesk_500Medium: require("../../assets/fonts/SpaceGrotesk-500Medium.ttf"),
11+
SpaceGrotesk_600SemiBold: require("../../assets/fonts/SpaceGrotesk-600SemiBold.ttf"),
12+
SpaceGrotesk_700Bold: require("../../assets/fonts/SpaceGrotesk-700Bold.ttf"),
13+
}
14+
15+
export const customFontsToLoad = FONT_FILES as Record<string, FontSource>
16+
17+
/**
18+
* On iOS and Android, the fonts are embedded as part of the app binary
19+
* using the expo config plugin in `app.json`. See the project
20+
* [`app.json`](../../app.json) for the expo-fonts configuration. The assets
21+
* are added via the `app/assets/fonts` folder. This config plugin
22+
* does NOT work for web, so we have to dynamically load the fonts via this hook.
23+
*
24+
* For more info: https://docs.expo.dev/versions/latest/sdk/font/
25+
*/
26+
export const useCustomFonts = (): [boolean, Error | null] => {
27+
if (Platform.OS === "web") {
28+
// eslint-disable-next-line react-hooks/rules-of-hooks
29+
return useFonts(customFontsToLoad)
30+
}
31+
32+
// On native, fonts are precompiled and ready
33+
return [true, null]
34+
}
2435

2536
const fonts = {
2637
spaceGrotesk: {
27-
// The way expo-fonts config plugin applies
28-
// fonts to the individual platforms, the names come out different
29-
// on ios and android. For web, we have to load fonts asynchronously
30-
// using useFonts.
3138
light: Platform.select({
3239
ios: "SpaceGrotesk-Light",
3340
android: "SpaceGrotesk-300Light",
34-
web: "spaceGroteskLight",
41+
web: "SpaceGrotesk_300Light",
3542
}),
3643
normal: Platform.select({
3744
ios: "SpaceGrotesk-Regular",
3845
android: "SpaceGrotesk-400Regular",
39-
web: "spaceGroteskRegular",
46+
web: "SpaceGrotesk_400Regular",
4047
}),
4148
medium: Platform.select({
4249
ios: "SpaceGrotesk-Medium",
4350
android: "SpaceGrotesk-500Medium",
44-
web: "spaceGroteskMedium",
51+
web: "SpaceGrotesk_500Medium",
4552
}),
4653
semiBold: Platform.select({
4754
ios: "SpaceGrotesk-SemiBold",
4855
android: "SpaceGrotesk-600SemiBold",
49-
web: "spaceGroteskSemiBold",
56+
web: "SpaceGrotesk_600SemiBold",
5057
}),
5158
bold: Platform.select({
5259
ios: "SpaceGrotesk-Bold",
5360
android: "SpaceGrotesk-700Bold",
54-
web: "spaceGroteskBold",
61+
web: "SpaceGrotesk_700Bold",
5562
}),
5663
},
5764
helveticaNeue: {

boilerplate/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"build:android:prod": "eas build --profile production --platform android --local"
3030
},
3131
"dependencies": {
32-
"@expo-google-fonts/space-grotesk": "^0.4.0",
3332
"@expo/metro-runtime": "~6.1.2",
3433
"@react-navigation/bottom-tabs": "^7.2.0",
3534
"@react-navigation/native": "^7.0.14",

boilerplate/src/app/_layout.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { useEffect, useState } from "react"
2-
import { Platform } from "react-native"
32
import { Slot, SplashScreen } from "expo-router"
4-
import { useFonts } from "@expo-google-fonts/space-grotesk"
53
import { KeyboardProvider } from "react-native-keyboard-controller"
64
import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context"
75

86
import { initI18n } from "@/i18n"
97
import { ThemeProvider } from "@/theme/context"
10-
import { customFontsToLoadWebOnly } from "@/theme/typography"
8+
import { useCustomFonts } from "@/theme/typography"
119
import { loadDateFnsLocale } from "@/utils/formatDate"
1210

1311
SplashScreen.preventAutoHideAsync()
@@ -22,11 +20,8 @@ if (__DEV__) {
2220
export { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary"
2321

2422
export default function Root() {
25-
// We load fonts dynamically for web only, the rest are handled by
26-
// the expo-font config plugin in `app.json`. If not using web,
27-
// you can delete this permissive check along with associated
28-
// code in `typography'.
29-
const [fontsLoadedWebOnly, fontErrorWebOnly] = useFonts(customFontsToLoadWebOnly)
23+
24+
const [areFontsLoaded, fontLoadError] = useCustomFonts()
3025
const [isI18nInitialized, setIsI18nInitialized] = useState(false)
3126

3227
useEffect(() => {
@@ -35,11 +30,11 @@ export default function Root() {
3530
.then(() => loadDateFnsLocale())
3631
}, [])
3732

38-
const loaded = Platform.OS === "web" ? fontsLoadedWebOnly && isI18nInitialized : isI18nInitialized
33+
const loaded = areFontsLoaded && isI18nInitialized
3934

4035
useEffect(() => {
41-
if (fontErrorWebOnly && Platform.OS === "web") throw fontErrorWebOnly
42-
}, [fontErrorWebOnly])
36+
if (fontLoadError ) throw fontLoadError
37+
}, [fontLoadError])
4338

4439
useEffect(() => {
4540
if (loaded) {

0 commit comments

Comments
 (0)