Skip to content

[Due for payment 2026-04-30] Defer eager styles computation to improve web startup time #87927

@roryabraham

Description

@roryabraham

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 OwnerCurrent Issue Owner: @BartekObudzinski

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2

Type

No type
No fields configured for issues without a type.

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions