Case study: Bluesky social-app
To test Hozo against a real production React Native codebase rather than synthetic benchmarks, the repository pins a checkout of Bluesky’s bluesky-social/social-app (007c893de107c2ecbf2188d618196075f19c8f5a) and runs two reproducible measurements against it:
pnpm measure:bluesky— a read-only static compiler audit over all 1,660 authored JS/TS files using@hozo/migration-audit.pnpm measure:bluesky:rnw-free— a production Expo Webpack build experiment that compiles app-owned.ts/.tsxthrough Hozo and blocksreact-nativeandreact-native-webmodule resolution to map the exact dependency boundary.
Both reports are committed in docs/measurements/ (bluesky-social-app.md and bluesky-rnw-free-build.md) so any change to compiler coverage or diagnostics shows up as a diff.
Part 1: Static compiler audit (pnpm measure:bluesky)
Section titled “Part 1: Static compiler audit (pnpm measure:bluesky)”Corpus scope
Section titled “Corpus scope”| Metric | Value |
|---|---|
| Repository & commit | bluesky-social/social-app @ 007c893de107c2ecbf2188d618196075f19c8f5a |
| Authored files | 1,660 (1,049 .tsx + 611 .ts, 7,216,111 bytes) |
| Platform split | 1,550 shared · 89 .web · 21 .native / .ios / .android |
| Styling surface | 651 files use style, 9 use className, 695 reference Bluesky’s atoms.* |
Headline results
Section titled “Headline results”- Zero parse or compile failures across all 1,660 files: extension-aware syntax selection parses
.tsxas TSX and.tsas TypeScript (so generic arrow functions such asasync <T>(...) =>in.tsfiles parse cleanly without false TSX probe rejections). - 1,193 Web components and 1,149 Native components lowered across 573 files: with 0
UNLOWERED_REACT_NATIVE_JSXerrors and 0 DOM style-array invariant violations ([styleA, styleB]passed raw to a DOM element). - The direct React Native JSX boundary on Web is closed: Web lowering retains 0 JSX tag occurrences bound to
react-nativeacross all 1,639 Web-eligible files. Every<View>,<Text>,<Pressable>,<ScrollView>,<FlatList>,<Image>,<TextInput>,<Modal>,<ActivityIndicator>, and<TouchableOpacity>in application JSX is lowered to semantic DOM or Hozo’s Web components. - 218 warnings, 0 errors:
135RN_COMPAT_NOT_INSTALLED(files where the Web compiler rewrotePlatform,StyleSheet,Keyboard, orDimensionsimports to@hozo/rn-compat, which the unmodified Bluesky checkout does not have installed).60MISSING_ACCESSIBILITY_LABELand17PRESSABLE_NEEDS_ROLE(interactive elements or inputs in Bluesky’s source that have no accessible name or role).6UNSUPPORTED_CLASS_ON_NATIVE(Web-specific class strings in the 9 files that useclassName).
What happens to react-native imports and values on Web
Section titled “What happens to react-native imports and values on Web”The audit’s binding-aware journals (reactNativeImportDecisions, reactNativeReferenceDecisions, and reactNativeValueDecisions) separate JSX tags from runtime value references:
- Imports (
662totalreact-nativeimport specifiers in Web targets):212moved to@hozo/rn-compat(StyleSheet,Platform,Keyboard,Dimensions,useWindowDimensions,AccessibilityInfo,PanResponder)28moved to@hozo/core(Pressable,TextInputused as runtime values)129type-only specifiers (StyleProp,ViewStyle,TextStyle,LayoutChangeEvent, etc.)293retained onreact-native(either JSX-only bindings whose tag uses were already lowered and removed, or platform services such asLinking,Share,AppState,InteractionManager, andLayoutAnimation)
- JSX tags (
4,132opening/closing tag occurrences in Web targets):4,117lowered directly to HTML or Hozo Web runtime helpers15preserved tag spellings whose import binding moved to@hozo/coreor@hozo/rn-compat0retained onreact-native
- Reviewed member contracts (
555non-JSX runtime references in completed Web files):358covered by reviewed@hozo/rn-compatmember contracts (StyleSheet.create,StyleSheet.flatten,StyleSheet.hairlineWidth,StyleSheet.absoluteFill,Platform.OS,Platform.select,Keyboard.dismiss,Dimensions.get)66retained with explicit migration guidance (Linking.openURL,Share.share,AppState.addEventListener,LayoutAnimation.configureNext,InteractionManager.runAfterInteractions)131unreviewed / whole-value references (such as passingVieworAnimatedinto third-party HOCs likeAnimated.createAnimatedComponent)
Part 2: Production Webpack RNW-free build experiment (pnpm measure:bluesky:rnw-free)
Section titled “Part 2: Production Webpack RNW-free build experiment (pnpm measure:bluesky:rnw-free)”Closing the direct JSX boundary in source files is necessary, but it is not the same as building a production Web bundle without react-native-web. To measure the full dependency graph honestly, scripts/measure-rnw-free-build.mjs runs Bluesky’s production Expo Webpack build three ways:
- Unmodified baseline: standard Expo Webpack production build.
- Hozo-lowered build: app-owned
src/**/*.{ts,tsx}runs through Hozo’s Web lowerer (unloweredReactNativeJsx: 'error') before Babel/Webpack. - Hozo-lowered + RNW blocked: the same Hozo-lowered build, but Webpack aliases
react-nativeandreact-native-webto an absent module so every remaining import request fails with an exact issuer trace.
What the three builds showed
Section titled “What the three builds showed”| Build | Webpack errors | Bundled react-native-web modules |
What it proves |
|---|---|---|---|
| 1. Unmodified baseline | 0 |
74 modules (430,839 unminified bytes) |
The baseline app builds cleanly and ships ~431 KB of react-native-web. |
2. Hozo-lowered (src/) |
0 |
71 modules |
Hozo’s Web output compiles and bundles cleanly inside Bluesky’s production Webpack pipeline. |
| 3. Hozo-lowered + RNW blocked | 274 errors (270 requests from 177 modules) |
0 modules |
Removing react-native-web entirely is blocked by 35 app modules (non-JSX platform APIs) and 138 modules across 40 third-party packages. |
Why 35 app modules and 40 third-party packages still requested react-native
Section titled “Why 35 app modules and 40 third-party packages still requested react-native”- Zero P0 Hozo core gaps in app code: after Hozo lowering and moving
Pressable,TextInput,FlatList,ScrollView, and@hozo/rn-compatAPIs, the P0 core-gap table (View,Pressable,FlatList,ScrollView,TextInput) and P1 foundation table (Dimensions,useWindowDimensions,useColorScheme,AccessibilityInfo) in reachable app modules are both 0. - The 35 app modules are platform services and imperative escape hatches:
Linking,AppState,Share,InteractionManager,LayoutAnimation,Alert,BackHandler,findNodeHandle, andStatusBar. These are non-UI OS services rather than view/style primitives. - The 138 third-party modules come from 40 external React Native libraries: led by
@sentry/react-native(24 modules),@react-navigation/elements(19),react-native-keyboard-controller(19),react-native-reanimated(9),@react-navigation/bottom-tabs(8),expo-image(5),react-native-progress(5),@react-navigation/native(4), andreact-native-svg(4). Because thosenode_modulespackages are published targetingreact-native, fully eliminatingreact-native-webfrom a bundle that keeps those libraries requires either Web-specific package entry points or bundler-level aliasing to@hozo/rn-compat.
Reproducing both measurements locally
Section titled “Reproducing both measurements locally”From a Hozo repository checkout:
# 1. Static migration audit (~10s warm; clones pinned social-app into temp/social-app on first run)pnpm measure:bluesky# 2. Production Webpack RNW-free boundary experiment (requires Node 24 for Bluesky's lockfile)mise exec node@24.19.0 -- pnpm measure:bluesky:rnw-free