An Elmish counter app running on Expo, using Fable to compile F# to JavaScript.
- .NET SDK (8.0+)
- Node.js (>= 22.11.0)
- For device/simulator testing:
- iOS: Xcode
- Android: Android Studio, Android SDK
npm install
dotnet tool restorenpm run watch— starts Fable in watch mode, recompiling F# to JS on save- In a separate terminal:
npm start— starts Expo dev server
Scan the QR code with Expo Go (Android) or Camera app (iOS) to run on a device, or press i for iOS simulator / a for Android emulator.
| Script | Command |
|---|---|
build |
One-shot Fable compile (release) |
watch |
Fable watch mode (debug) |
start |
Start Expo dev server |
ios |
Build and run on iOS |
android |
Build and run on Android |
| Tool | Version |
|---|---|
| Expo SDK | 55 |
| React Native | 0.83.4 |
| Fable | 4.29.0 (local tool via dotnet-tools.json) |
| Fable.Elmish | 5.0.2 |
| Fable.React.Native | 4.0.0 |
src/App.fs— Elmish model, update, and view (usesProgram.withExpo)src/Styles.fs— React Native stylessrc/app.fsproj— F# project with NuGet package referencesout/— Fable-compiled JavaScript output (gitignored)assets/— App icons and splash screen imagesshims/empty.js— Stub for unused optional Fable.ReactNative dependenciesapp.json— Expo configurationmetro.config.js— Metro bundler configuration (includes shims for unused RN packages)
This sample uses Elmish.Expo.Program.withExpo instead of Elmish.ReactNative.Program.withReactNative. This integrates with Expo's registerRootComponent which provides:
- Expo dev tools overlay
- Proper web platform rendering support
Expo.fxside-effect initialization
No index.js entry point is needed — Expo reads the "main" field from package.json which points directly to the Fable-compiled output.