diff --git a/docs.json b/docs.json index 462bd572..64303718 100644 --- a/docs.json +++ b/docs.json @@ -615,6 +615,7 @@ "runtimes/react-native/layouts", "runtimes/react-native/state-machines", "runtimes/react-native/data-binding", + "runtimes/react-native/typescript-schemas", "runtimes/react-native/loading-assets", "runtimes/react-native/fonts", "runtimes/react-native/caching-a-rive-file", diff --git a/runtimes/react-native/typescript-schemas.mdx b/runtimes/react-native/typescript-schemas.mdx new file mode 100644 index 00000000..ef965468 --- /dev/null +++ b/runtimes/react-native/typescript-schemas.mdx @@ -0,0 +1,215 @@ +--- +title: "TypeScript Schemas" +description: "Generate TypeScript types from your .riv files so artboard, state machine, view model, and property names are checked at compile time." +--- + + + TypeScript schema generation requires the new React Native runtime, `@rive-app/react-native` `v0.5` beta or later. See the [migration guide](/runtimes/react-native/migration-guide) to upgrade. + + +`@rive-app/react-native` can generate TypeScript schemas for your `.riv` files, so that artboard names, state machine names, view model names, property paths, and enum values are all checked at compile time. A typo in any of them becomes a TypeScript error instead of a silent runtime failure — and your editor autocompletes them. + +Schemas are opt-in per asset. Generate one for an asset and you get autocomplete and compile-time checking everywhere that asset is used; skip it and the asset keeps accepting plain strings, exactly as before. You can adopt this one file at a time. + +A schema is a `.d.ts` declaration file, so nothing is added to your bundle and no runtime code changes. + +## Quick start + +### 1. Install the introspection dependency + +```sh +yarn add -D @rive-app/canvas +``` + +The generator uses Rive's WASM runtime to inspect `.riv` files. It is only needed on the machine running codegen — it is never bundled into your app. + +### 2. Enable arbitrary extensions in `tsconfig.json` + +```json +{ + "compilerOptions": { + "allowArbitraryExtensions": true + } +} +``` + +This lets TypeScript resolve `my.riv.d.ts` when you `import asset from './my.riv'`. If you already have `'riv'` in Metro's `assetExts`, that part is unchanged. + +### 3. Generate a schema next to each asset + +```sh +npx rive-gen-types assets/rive/rewards.riv # one file → rewards.riv.d.ts +npx rive-gen-types --all assets/rive # every .riv in a directory +``` + +This writes a `rewards.riv.d.ts` file next to the asset. Commit these files — they are deterministic and human-readable, and regenerating produces identical output for an unchanged asset. + +### 4. Import and use + +```tsx +import rewardsRiv from './assets/rive/rewards.riv'; + +const { riveFile } = useRiveFile(rewardsRiv); +``` + +Everything below is now compile-checked. + +## What gets typed + +A generated schema looks like this: + +```ts rewards.riv.d.ts +// generated — do not edit +import type { RiveAsset } from '@rive-app/react-native'; + +declare const asset: RiveAsset<{ + artboards: 'Main' | 'Lives 2' | 'Chest' | 'Button' | 'Heart' | 'Item'; + defaultArtboard: 'Main'; + stateMachines: { + 'Main': 'State Machine 1'; + // ...one entry per artboard + }; + viewModels: { + Rewards: { + Price_Value: 'number'; + Coin: 'viewModel:Item_Icon_Value'; + // ... + }; + Item: { + Item_Selection: 'enum:Coin|Gem'; + }; + // ... + }; +}>; + +export default asset; +``` + +Everything flows from the import — no manual type annotations needed. + +Artboard and state machine names are constrained to the file's contents: + +```tsx + +``` + +View model names are constrained: + +```tsx +const { instance } = useViewModelInstance(riveFile, { + viewModelName: 'Rewards', // ✗ 'Rewrads' is a compile error + async: true, +}); +``` + +Property paths are constrained per kind, including nested paths: + +```tsx +const { value, setValue } = useRiveNumber('Price_Value', instance); // ✓ +useRiveNumber('Coin/Item_Value', instance); // ✓ nested +useRiveNumber('Coin', instance); // ✗ Coin is a view model ref, not a number +useRiveTrigger('Price_Value', instance); // ✗ wrong kind +``` + +Enum values are exact unions: + +```tsx +const { value: kind, setValue: setKind } = useRiveEnum( + 'Item_Selection/Item_Selection', + instance +); +// kind: 'Coin' | 'Gem' + +setKind('Gem'); // ✓ +setKind('Diamond'); // ✗ compile error +``` + +Instance accessors are typed the same way: + +```ts +instance.numberProperty('Coin/Item_Value'); // ✓ +instance.enumProperty('Item_Selection/Item_Selection')?.set('Gem'); // ✓ typed values +instance.viewModel('Coin')?.numberProperty('Item_Value'); // ✓ typed nesting +``` + +## Typing your own components + +Use `TypedRiveFile` and `TypedViewModelOf` to keep type safety across component boundaries: + +```tsx +import { + type TypedRiveFile, + type TypedViewModelOf, +} from '@rive-app/react-native'; +import rewardsRiv from './assets/rive/rewards.riv'; + +function RewardsScreen({ file }: { file: TypedRiveFile }) { + // artboardName / viewModelName / paths are still checked here +} + +function PricePanel({ + instance, +}: { + instance: TypedViewModelOf; +}) { + const { value } = useRiveNumber('Price_Value', instance); + // ... +} +``` + +## Workflow + +- **Regenerate after editing a `.riv` in the Rive editor**, then fix whatever TypeScript now flags. Renamed artboards, removed properties, and changed enum values all surface as compile errors at the exact call sites. +- **Commit the `.riv.d.ts` files** alongside the assets. +- **Optionally guard in CI** — fail the build when schemas drift out of sync: + +```yaml +- name: Validate .riv schemas + run: | + npx rive-gen-types --all assets/rive + if [ -n "$(git status --porcelain -- 'assets/rive/*.riv.d.ts')" ]; then + echo ".riv.d.ts files are out of date — run 'npx rive-gen-types --all assets/rive' and commit." + exit 1 + fi +``` + +## Behavior and limits + +- **No schema, no problem.** Files loaded without a generated schema (via `require(...)`, URLs, or `RiveFileFactory.*`) accept any string everywhere, exactly as before. Type safety is opt-in per asset. +- **Files without view models** still get typed artboard and state machine names. +- **Nested paths are typed up to two hops** (`'a/b/leaf'`). Deeper paths are valid at runtime but need an untyped instance or a cast. +- **Dynamic paths.** A typed instance intentionally rejects `string` variables as paths. If you build paths at runtime, keep the instance untyped for those call sites. +- **List elements are untyped** (`ViewModelInstance`) — a `.riv` schema cannot know which view model a list holds at a given index. +- **Enum member names containing `|`** fall back to an untyped `enum`, and the generator warns. + +## CLI reference + +```sh +npx rive-gen-types # writes .riv.d.ts next to the asset +npx rive-gen-types --out # explicit output path (required for URLs) +npx rive-gen-types --all # every .riv in a directory, recursively +``` + +The exit code is non-zero if any file fails — for example a corrupt file, a file with no artboards, or a missing `@rive-app/canvas` — along with a per-file error report. + +## Troubleshooting + + + + Install the dev-time introspection dependency: + + ```sh + yarn add -D @rive-app/canvas + ``` + + + Check that a `.riv.d.ts` file exists next to the asset, and that `allowArbitraryExtensions` is enabled in the `tsconfig.json` that covers the importing file. + + + That's the feature working. Regenerate the schema with `npx rive-gen-types …` and follow the compile errors to every call site that referenced the renamed or removed item. + +