@react-native-media-styles/expo
v0.1.0
Published
Build-time CSS extraction and route-based stylesheet delivery for Expo.
Readme
@react-native-media-styles/expo
Build-time CSS extraction and route-based stylesheet delivery for Expo.
Experimental: This integration depends on version-specific Expo internals. Its API and generated output may change between releases. Pin Expo and this package, and rerun the SSR, navigation, and browser checks when upgrading.
Install @react-native-media-styles/expo with @react-native-media-styles/core. Apply withMediaStyles to the Expo Metro configuration.
import {getDefaultConfig} from 'expo/metro-config';
import {withMediaStyles} from '@react-native-media-styles/expo';
module.exports = withMediaStyles(getDefaultConfig(__dirname));- Apply
@react-native-media-styles/expoafter other Metro configuration wrappers. - The Babel extractor in
@react-native-media-styles/expocompiles static React Native Web and breakpoint declarations. The Metro worker supplies the generated CSS to Expo's asset serializer. - Expo exports content-hashed CSS files and includes their links in the server document.
- Expo's CSS development loader replaces styles during Fast Refresh.
- Metro's resolved dependency graph supplies shared CSS and separate CSS bundles for route and layout modules. Each bundle includes its lazy dependencies and components rendered only in the browser.
- Server responses link shared CSS and the bundles selected by Expo Router's URL matching, including layouts and initial navigator routes.
- Client navigation waits for the destination route's stylesheet before rendering its content. A timed, bounded retry handles transient failures.
- Stylesheet names contain a content hash. The client route registry is emitted as a separate content-hashed script, so production does not rely on an inline registry and unchanged bundles retain their URLs for caching.
@react-native-media-styles/expocombines generated layer blocks and keeps the last occurrence of identical rules within each layer. Imported CSS and media rules preserve their positions.- Production CSS contains route asset metadata. Development CSS also includes module paths for Fast Refresh updates.
- Static
StyleSheet.createandbreakpointsdeclarations generate CSS assets. Runtime RNW style values use inline styles. - Breakpoint hooks compose references to existing CSS declarations supplied by
@react-native-media-styles/expo. - Development serves route CSS as files and applies Fast Refresh updates to modules used by active routes.
- Restart Metro with --clear after configuration changes and changes to imported breakpoint constants.
@react-native-media-styles/expoimplementation and tests use TypeScript. Builds emit CommonJS modules for Metro and Babel.
@react-native-media-styles/expo preserves the configured Expo worker, Babel transformer and resolver. Extraction, route asset selection and development updates are internal modules of @react-native-media-styles/expo.
Supported React Native Web combination
The supported and continuously tested web combination is:
- Expo
58.0.14 - React Native
0.88.0 - React Native Web
0.21.4 - React
19.3.0
The package peer dependency pins the associated React Native Web and Expo Metro versions. Verify upgrades through server rendering, client navigation, delayed content, Fast Refresh and all browser projects.
Production deployment contract
The library emits the assets and manifest. The application or CDN owns HTTP delivery.
- Serve
/_expo/static/**withCache-Control: public, max-age=31536000, immutable. - Compress HTML, JavaScript, JSON and CSS with Brotli or gzip.
- Do not publicly cache personalized or authenticated HTML.
- Preserve the manifest script before the Expo entry bundle.
- Serve missing hashed assets as 404 responses rather than HTML fallbacks.
The example production server implements this contract and production.spec.ts verifies the actual response headers. Do not deploy Expo's development serve output as-is: it does not provide the production cache and compression policy.
The client manifest is an external script-src 'self' asset. It does not add an inline script that requires a CSP nonce.
Stylesheet failure recovery
Routes should export an Expo Router ErrorBoundary that clears the failed stylesheet record before retrying:
import {retryRouteCSS} from '@react-native-media-styles/expo/client';
import type {ErrorBoundaryProps} from 'expo-router';
export function ErrorBoundary({retry}: ErrorBoundaryProps) {
return (
<button
onClick={() => {
retryRouteCSS();
retry();
}}
>
Try again
</button>
);
}The default load policy waits 10 seconds per attempt and makes three total attempts with exponential backoff. Applications can adjust it in their client bootstrap with configureRouteCSS({timeoutMs, retries, retryDelayMs}).
Route CSS scope
Route CSS selection applies to streaming server rendering and subsequent client navigation. Shared entry dependencies supply application-wide CSS. A route bundle contains every stylesheet reachable through its resolved imports, including conditional and asynchronous imports. Dependencies shared by route modules can appear in multiple route bundles.
Expo's single-page and static export HTML currently includes the complete CSS asset list. Native builds use the host pipeline.
@react-native-media-styles/expo transforms Expo's server renderer and route rendering functions. It checks the expected function shapes during compilation and reports an error when those integration points change. Route matching builds Expo Router's route table once per server process and reuses it across requests. Verify Expo upgrades with server rendering, delayed content, navigation and Fast Refresh tests.
Compiler syntax caveat
@react-native-media-styles/expo recognizes static style declarations in ES modules and CommonJS modules, including Babel object-spread helpers in both formats. It evaluates recognized syntax without executing application modules.
Compiler versions and build settings can produce different syntax for the same style declaration. Each additional helper or expression shape needs explicit handling and regression coverage. AMD, UMD, IIFE and System.register bundles are outside the supported extraction inputs.
An unrecognized expression can prevent extraction even when its values are constant at runtime. Ordinary RNW styles retain validated inline values. Styles that require CSS, including $raw rules and keyframes, and breakpoint definitions report an extraction error when their values cannot be resolved at build time.
Verify dependency upgrades through the application's Metro pipeline, including SSR and browser builds. Regression tests should exercise the dependency files and compiler output used by those builds.
