Proposal: Defer eager styles computation to improve web startup time
Background
The Expensify App's styles system generates the complete set of theme-aware styles through a styles(theme) function that processes ~6,500 lines of style definitions, including StyleSheet.create() calls, dynamic style functions, and utility spreads.
This function is invoked at render time by ThemeStylesProvider, a React context provider near the root of the component tree, which computes styles for the active theme and makes them available to all child components via useThemeStyles() and useStyleUtils() hooks. Both hooks throw an error if accessed outside the provider, meaning the context's default value is never read in production.
The styles module also pulls in heavy transitive dependencies: react-native-reanimated, @rnmapbox/maps, react-map-gl, and the 9,600-line CONST module. On web, JavaScript modules are parsed and evaluated synchronously at import time, so any top-level computation in a module and its transitive imports runs before the application can render its first frame.
Problem:
When the styles module is imported, it eagerly calls:
styles(defaultTheme)
createStyleUtils(defaultTheme, defaultStyles)
at the top level to populate context default values that are never read in production, adding a redundant ~111ms of blocking JavaScript evaluation to web startup before any component renders.
Solution:
Remove the eager:
const defaultStyles = styles(defaultTheme) from src/styles/index.ts
const DefaultStyleUtils = createStyleUtils(defaultTheme, defaultStyles) from src/styles/utils/index.ts
Replace the context default values in ThemeStylesContextProvider/default.ts with a lazy Proxy that defers the styles(defaultTheme) computation to first property access. In production, ThemeStylesProvider always supplies real values, so the Proxy is never triggered and the redundant computation is eliminated entirely. Tests that render components without the provider still get real styles on demand through lazy initialization. The only other consumers of the removed defaultStyles export were five Storybook files, updated to call styles(defaultTheme) locally.
Measured web startup results (dev environment, 10 samples each):
Average: 3,496ms → 3,231ms (-265ms, -7.6%)
Median: 3,418ms → 3,222ms (-196ms, -5.7%)
Standard deviation: 213ms → 110ms.
This makes startup both faster and more consistent.
Issue Owner
Current Issue Owner: @BartekObudzinski
Proposal: Defer eager styles computation to improve web startup time
Background
The Expensify App's styles system generates the complete set of theme-aware styles through a
styles(theme)function that processes ~6,500 lines of style definitions, includingStyleSheet.create()calls, dynamic style functions, and utility spreads.This function is invoked at render time by
ThemeStylesProvider, a React context provider near the root of the component tree, which computes styles for the active theme and makes them available to all child components viauseThemeStyles()anduseStyleUtils()hooks. Both hooks throw an error if accessed outside the provider, meaning the context's default value is never read in production.The styles module also pulls in heavy transitive dependencies:
react-native-reanimated,@rnmapbox/maps,react-map-gl, and the 9,600-lineCONSTmodule. On web, JavaScript modules are parsed and evaluated synchronously at import time, so any top-level computation in a module and its transitive imports runs before the application can render its first frame.Problem:
When the styles module is imported, it eagerly calls:
styles(defaultTheme)createStyleUtils(defaultTheme, defaultStyles)at the top level to populate context default values that are never read in production, adding a redundant ~111ms of blocking JavaScript evaluation to web startup before any component renders.
Solution:
Remove the eager:
const defaultStyles = styles(defaultTheme)fromsrc/styles/index.tsconst DefaultStyleUtils = createStyleUtils(defaultTheme, defaultStyles)fromsrc/styles/utils/index.tsReplace the context default values in
ThemeStylesContextProvider/default.tswith a lazyProxythat defers thestyles(defaultTheme)computation to first property access. In production,ThemeStylesProvideralways supplies real values, so theProxyis never triggered and the redundant computation is eliminated entirely. Tests that render components without the provider still get real styles on demand through lazy initialization. The only other consumers of the removeddefaultStylesexport were five Storybook files, updated to callstyles(defaultTheme)locally.Measured web startup results (dev environment, 10 samples each):
Average: 3,496ms → 3,231ms (-265ms, -7.6%)
Median: 3,418ms → 3,222ms (-196ms, -5.7%)
Standard deviation: 213ms → 110ms.
This makes startup both faster and more consistent.
Issue Owner
Current Issue Owner: @BartekObudzinski