yarn-spinner-runner-ts
Version:
TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts)
410 lines (318 loc) • 12.7 kB
Markdown
# yarn-spinner-runner-ts
TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter.
* [Github repository](https://github.com/oleksii-chekhovskyi/yarn-spinner-runner-ts) for more information.
* [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts)
## References
* Old JS parser: `bondage.js` (Yarn 2.x) — [GitHub](https://github.com/mnbroatch/bondage.js/tree/master/src)
* Official compiler (C#): YarnSpinner.Compiler — [GitHub](https://github.com/YarnSpinnerTool/YarnSpinner/tree/main/YarnSpinner.Compiler)
* Existing dialogue runner API: YarnBound — [GitHub](https://github.com/mnbroatch/yarn-bound?tab=readme-ov-file)
## Features
* ✅ Full Yarn Spinner 3.x syntax support
* ✅ Parser for `.yarn` files → AST
* ✅ Compiler: AST → Intermediate Representation (IR)
* ✅ Runtime with `YarnRunner` class
* ✅ React hook: `useYarnRunner()`
* ✅ React components: `<DialogueView />`, `<DialogueScene />`, `<DialogueExample />`
* ✅ Typing animation with configurable speeds, cursor styles, and auto-advance controls
* ✅ Markup parsing with HTML formatting tags and CSS-ready spans
* ✅ Expression evaluator for conditions
* ✅ Command system with built-in handlers (`<<set>>`, `<<declare>>`, etc.)
* ✅ Scene system with backgrounds and actor images (with configurable portrait cross-fades)
* ✅ Custom CSS styling via `&css{}` attributes
* ✅ Built-in functions (`visited`, `random`, `min`, `max`, etc.)
* ✅ Support for:
* Lines with speakers
* Options with indented bodies
* Inline option conditions via `[if expression]`
* `<<if>>/<<elseif>>/<<else>>/<<endif>>` blocks
* `<<once>>...<<endonce>>` blocks
* `<<jump NodeName>>` commands
* `<<detour NodeName>>` commands
* Variables and expressions
* Enums (`<<enum>>` blocks)
* Smart variables (`<<declare $var = expr>>`)
* Node groups with `when:` conditions
* Tags and metadata on nodes, lines, and options
* Custom commands
## Installation
```bash
npm install
npm run build
```
## Quick Start
### Basic Usage
```typescript
import { parseYarn, compile, YarnRunner } from "yarn-spinner-runner-ts";
const yarnText = `
title: Start
---
Narrator: Hello!
-> Option 1
Narrator: You chose option 1.
-> Option 2
Narrator: You chose option 2.
===
`;
const ast = parseYarn(yarnText);
const program = compile(ast);
const runner = new YarnRunner(program, {
startAt: "Start",
variables: { score: 10 },
functions: {
add: (a: number, b: number) => a + b,
},
handleCommand: (cmd, parsed) => {
console.log("Command:", cmd);
},
onStoryEnd: ({ variables, storyEnd }) => {
console.log("Story ended!", storyEnd);
console.log("Final variables:", variables);
},
});
// Get current result
console.log(runner.currentResult); // TextResult, OptionsResult, or CommandResult
// Advance dialogue
runner.advance(); // Continue text
runner.advance(0); // Choose option 0
```
> Variables passed from your host app can be provided as `score` or `$score`; the runner normalizes keys so either style works.
### Inline conditional options
You can add a per-option condition by appending `[if expression]` to the option text. The expression is evaluated when the option list is emitted; options whose expression evaluates to `false` are dropped before the runner shows them.
```yarn
title: Hub
---
<<declare $hasBadge = false>>
-> Ask about the badge [if $hasBadge]
Narrator: You flash the badge.
-> Offer a bribe
Narrator: You slide some eddies across the table.
===
```
Once some branch executes `<<set $hasBadge = true>>`, the badge option automatically appears alongside the other entries, without extra `<<if>>` blocks.
### Arithmetic assignments
`<<set>>` accepts both `to` and `=` aliases and evaluates the expression on the right-hand side, so you can modify variables inline—operator precedence and parentheses all work the same way they do in Yarn Spinner:
```yarn
<<set $reputation = $reputation - 25 >>
<<set $score = ($score + 10) / 2>>
Narrator: Current street cred: {$reputation}, score: {$score}
```
### React Usage
```tsx
import { parseYarn, compile, useYarnRunner, DialogueView } from "yarn-spinner-runner-ts";
import { parseScenes } from "yarn-spinner-runner-ts";
import type { SceneCollection } from "yarn-spinner-runner-ts";
function MyDialogue() {
const [program] = useState(() => {
const ast = parseYarn(yarnText);
return compile(ast);
});
const [scenes] = useState<SceneCollection>(() => {
return parseScenes(sceneYamlText);
});
const { result, advance } = useYarnRunner(program, {
startAt: "Start",
variables: { score: 10 },
});
return (
<DialogueView
result={result}
onAdvance={advance}
scenes={scenes}
/>
);
}
```
### Full Example Component
```tsx
import { DialogueExample } from "yarn-spinner-runner-ts";
function App() {
return <DialogueExample />;
}
```
### Typing Animation
Set `enableTypingAnimation` on `DialogueView` to enable the `TypingText` component for typewriter-style delivery. Tweak props like `typingSpeed`, `showTypingCursor`, `cursorCharacter`, `autoAdvanceAfterTyping`, `autoAdvanceDelay`, and `pauseBeforeAdvance` to fine-tune behaviour, and see [Typing Animation (React)](./docs/typing-animation.md) for details.
### Browser Demo
Run the interactive browser demo:
```bash
npm run demo
```
This starts a Vite dev server with a live Yarn script editor and dialogue system.
## API Reference
### Parser
* `parseYarn(text: string): YarnDocument` — Parse Yarn script text into AST
### Compiler
* `compile(doc: YarnDocument, opts?: CompileOptions): IRProgram` — Compile AST to IR
### Runtime
* `YarnRunner(program: IRProgram, options: RunnerOptions)` — Dialogue runner class
* `currentResult: RuntimeResult | null` — Current dialogue state
* `advance(optionIndex?: number): void` — Advance dialogue
* `getVariable(name: string): unknown` — Get variable value
* `setVariable(name: string, value: unknown): void` — Set variable value
* `getVariables(): Readonly<Record<string, unknown>>` — Get all variables
* `onStoryEnd?: (payload: { variables: Readonly<Record<string, unknown>>; storyEnd: true }) => void` — Handler called when story reaches its end, providing final variable state
### React Components
* `useYarnRunner(program: IRProgram, options: RunnerOptions)` — React hook
* Returns: `{ result: RuntimeResult | null, advance: (optionIndex?: number) => void, runner: YarnRunner }`
* `<DialogueView result={...} onAdvance={...} scenes={...} />` — Ready-to-use dialogue component
* `<DialogueScene sceneName={...} speaker={...} scenes={...} actorTransitionDuration={...} /> — Scene background, actor display, and portrait transitions` — Scene background and actor display
* `<DialogueExample />` — Full example with editor
### Scene System
* `parseScenes(input: string | Record<string, unknown>): SceneCollection` — Parse YAML scene configuration
* `SceneCollection` — Type for scene configuration
* `SceneConfig` — Type for individual scene config
* `ActorConfig` — Type for actor configuration
See [Scene and Actor Setup Guide](./docs/scenes-actors-setup.md) for detailed documentation.
### Expression Evaluator
* `ExpressionEvaluator(variables, functions, enums?)` — Safe expression evaluator
* Supports: `===`, `!==`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `!`
* Operator aliases: `eq/is`, `neq`, `gt`, `lt`, `lte`, `gte`, `and`, `or`, `not`, `xor`
* Function calls: `functionName(arg1, arg2)`
* Variables, numbers, strings, booleans
* Enum support with shorthand (`MyEnum.Case`)
### Commands
* `CommandHandler` — Command handler registry
* Built-in: `<<set variable = value>>`, `<<declare $var = expr>>`
* Register custom handlers: `handler.register("mycommand", (args) => { ... })`
* `parseCommand(content: string): ParsedCommand` — Parse command string
### Built-in Functions
The runtime includes these built-in functions:
* `visited(nodeName)` — Check if a node was visited
* `visited_count(nodeName)` — Get visit count for a node
* `random()` — Random float 0-1
* `random_range(min, max)` — Random integer in range
* `dice(sides)` — Roll a die
* `min(a, b)`, `max(a, b)` — Min/max values
* `round(n)`, `round_places(n, places)` — Rounding
* `floor(n)`, `ceil(n)` — Floor/ceiling
* `inc(n)`, `dec(n)` — Increment/decrement
* `decimal(n)` — Convert to decimal
* `int(n)` — Convert to integer
* `string(n)`, `number(n)`, `bool(n)` — Type conversions
## Example Yarn Script
```yarn
title: Start
tags: #introduction #tutorial
---
Narrator: Welcome!
<<set score = 10>>
<<if score >= 10>>
Narrator: High score!
<<else>>
Narrator: Low score.
<<endif>>
<<declare $randomName = random_range(1, 3) == 1 ? "Alice" : "Bob">>
Narrator: Your name is {$randomName}.
-> Ask about features
Player: What can this do?
Narrator: Lots of things!
-> Ask about commands
Player: Tell me about commands.
Narrator: Commands modify state.
<<once>>
Narrator: This only shows once!
<<endonce>>
<<jump NextNode>>
===
title: NextNode
scene: scene1
---
Narrator: You've arrived at the next scene!
===
```
## CSS Styling
You can apply custom CSS styles to nodes and options using the `&css{}` attribute:
```yarn
title: StyledNode
&css{background-color: #ff0000; color: white;}
---
Narrator: This node has a red background.
-> Option 1 &css{background-color: blue;}
Narrator: You chose the blue option.
===
```
Styles are merged with default styles, with custom styles taking precedence.
See [CSS Attribute Documentation](./docs/css-attribute.md) for details.
## Scene Configuration
Configure scenes and actors using YAML:
```yaml
scenes:
scene1:
background: https://example.com/background1.jpg
actors:
special_npc:
image: https://example.com/special-npc.png
actors:
Narrator: https://example.com/narrator.png
Player: https://example.com/player.png
```
Use scenes in Yarn nodes:
```yarn
title: MyNode
scene: scene1
---
Narrator: This scene uses scene1's background and actors.
===
```
See [Scene and Actor Setup Guide](./docs/scenes-actors-setup.md) for complete documentation.
## Project Structure
```
yarn-spinner/
├── src/
│ ├── model/ # AST types
│ ├── parse/ # Lexer and parser
│ ├── compile/ # Compiler (AST → IR)
│ ├── runtime/ # Runtime execution
│ ├── scene/ # Scene system
│ ├── react/ # React components
│ └── tests/ # Test files
├── examples/
│ ├── yarn/ # Example Yarn scripts
│ ├── browser/ # Browser demo (Vite)
│ └── scenes/ # Scene configuration examples
├── docs/ # Documentation
└── dist/ # Compiled output
```
## Development
```bash
npm run build # Build TypeScript
npm run dev # Watch mode
npm run lint # Run ESLint
npm test # Run tests
npm run demo # Start browser demo
npm run demo:build # Build browser demo
```
## Testing
Tests are located in `src/tests/` and cover:
* Basic dialogue flow
* Options and branching
* Variables and flow control
* Commands (`<<set>>`, `<<declare>>`, etc.)
* `<<once>>` blocks
* `<<jump>>` and `<<detour>>`
* Full featured Yarn scripts
Run tests:
```bash
npm test
```
## Documentation
Additional documentation is available in the `docs/` folder:
* [Lines, Nodes, and Options](./docs/lines-nodes-and-options.md)
* [Options](./docs/options.md)
* [Jumps](./docs/jumps.md)
* [Detour](./docs/detour.md)
* [Logic and Variables](./docs/logic-and-variables.md)
* [Flow Control](./docs/flow-control.md)
* [Once Blocks](./docs/once.md)
* [Smart Variables](./docs/smart-variables.md)
* [Enums](./docs/enums.md)
* [Commands](./docs/commands.md)
* [Functions](./docs/functions.md)
* [Node Groups](./docs/node-groups.md)
* [Tags and Metadata](./docs/tags-metadata.md)
* [CSS Attribute](./docs/css-attribute.md)
* [Typing Animation (React)](./docs/typing-animation.md)
* [Markup (Yarn Spinner)](./docs/markup.md)
* [Actor Image Transitions](./docs/actor-transition.md)
* [Scene and Actor Setup](./docs/scenes-actors-setup.md)
## License
MIT