12 · Expo
The TypeScript helpers an Expo app shares between screens (theme tokens,
text and date formatting) plus a tooling script that validates app.json.
All of it runs under 3va without Metro or an emulator.
Source: examples/12-expo
app.json Expo config (validated by the script below)
src/
theme.ts light/dark palettes, spacing, radius, font sizes
text.ts truncate, pluralize, capitalize, slug, formatCount
date.ts formatRelative ("hace 5min", "en 3d"), formatShortDate
index.ts runnable demo
validate-app.ts app.json checker
expo.test.ts 18 tests incl. a theme snapshotRun the demo
cd examples/12-expo
3va run src/index.ts --allow-read=./src=== Expo helpers, ejecutados con 3va ===
Tema claro (primary): #007AFF
Tema oscuro (primary): #0A84FF
Espaciado SM: 8 px
Radio LG: 20 px
H1 font-size: 32 px
Texto truncado: Esta cadena es bastante larga…
Plural: ha enviado 1 mensaje
Capitalizar: Exp-router
Conteo 15200: 15.2K
Conteo 2300000: 2.3M
Formato relativo:
ahora: ahora
hace 5min: hace 5min
hace 2h: hace 2h
en 3d: en 3d
Fecha corta: 11/09/2026Validate app.json
3va run src/validate-app.ts --allow-read=./app.json ✓ app.json válido — expo-3va-demo v1.0.0
slug: expo-3va-demo
scheme: expodemoWith a broken config ({"expo":{"name":"x","version":"1.0"}}) it exits with
code 1:
[WARN] expo.scheme no está definido — deep links no funcionarán
[ERROR] ✗ expo.slug es requerido (usado por Expo Router)
[ERROR] ✗ expo.version '1.0' no sigue semverThat makes it usable as a pre-build check in CI.
Run the tests
3va test # 18 passedThe last test snapshots the whole dark palette into
src/__snapshots__/expo.test.ts.snap.json. If anyone changes a color by
accident, the test fails.
Scaffold a real Expo app
3va create expo-app my-app # runs create-expo-app, like `npm create`
cd my-app3va create <pkg> works like npm create / bun create. Extra arguments
go to the scaffolder, e.g. 3va create expo-app my-app --template default@sdk-57.
Things to keep in mind
- Each script needs only the reads it does.
index.tsneeds./srcfor its imports.validate-app.tsimports nothing, so./app.jsonalone is enough. Without it:Permission denied: --allow-read=…/app.json is required. validate-app.tsmixesrequire()with TypeScript. 3va acceptsconst fs = require("fs")inside a.tsfile.tscwould want@types/nodefor that.console.warnandconsole.errorget[WARN]/[ERROR]prefixes in non-interactive runs like the one above. Keep that in mind if you parse the output in CI.- Tests that depend on the clock (
formatRelative(Date.now() - 3600_000)→"hace 1h") pass because they compute relative to “now”. Absolute dates (formatShortDate) use the machine’s local time zone, so a test fixed to a UTC timestamp could fail in a different time zone. - What 3va doesn’t do for Expo: Metro bundling, Expo Router, native modules and EAS builds stay with the Expo toolchain. 3va covers the pure-TS helpers, scripts and tests.