Skip to content

feat: native yoga-layout via taffy + JSON module imports — ink #348 end-to-end#5038

Merged
proggeramlug merged 3 commits into
mainfrom
yoga-taffy
Jun 12, 2026
Merged

feat: native yoga-layout via taffy + JSON module imports — ink #348 end-to-end#5038
proggeramlug merged 3 commits into
mainfrom
yoga-taffy

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Makes ink (React-based TUI) compile and render fully natively — no WASM yoga, no V8. Three independent changes under #348.

╭──────────────────────────────────────╮
│                                      │
│ Native ink via taffy                 │
│                                      │
│ left col            right col        │
│                                      │
╰──────────────────────────────────────╯

(render(<App/>) → positioned ANSI from a single native executable.)

1. Native perry/yoga backend over taffy

yoga-layout (the WASM flexbox engine ink uses for geometry) is replaced by a native backend on taffy 0.7 (already a perry-runtime dep).

  • crates/perry-runtime/src/yoga.rs — handle-based node store with the full FFI surface ink uses (node new/free, insert/remove/child-count, set number/edge/gap/enum, measure-func register/unregister, calculateLayout, computed box/edge getters). calculateLayout builds a fresh TaffyTree, wires per-node measure callbacks back into JS via js_native_call_value, and runs compute_layout_with_measure. A GC root scanner marks live measure callbacks.
  • crates/perry-codegen/src/lower_call/native_table/yoga.rs — 14 NativeModSig rows → runtime entry points (registered in native_table/mod.rs).
  • perry/yoga added to the perry-namespace module list in perry-api-manifest.
  • node_modules/yoga-layout/src/index.ts is a thin shim re-exporting the native primitives behind the real yoga Node/Config API.

2. JSON module imports → native default export

import data from "./x.json" (with or without with { type: "json" }) was skipped entirely during module collection, leaving the default import bound to the empty-module sentinel. ink's cli-boxes (import cliBoxes from "./boxes.json") thus resolved to undefined, and <Box borderStyle="round"> threw Cannot read properties of undefined (reading 'topLeft').

collect_modules.rs now materializes a JSON import as a native ESM module: validate as JSON, then compile export default <json>; through the normal parse → lower → codegen path. Non-JSON modules take an identical code path (the change only removes is_json from a disjunction that was already false for them). Malformed JSON now fails with a clear Failed to parse JSON module … error.

3. Labeled break from a nested switch

A labeled switch didn't register its label as a break target, so break <label>; from a nested switch (react-reconciler's createFiberFromTypeAndProps) escaped to the wrong exit — dropping Context.Provider children, so ink rendered nothing. switch_stmt.rs now registers the consumed label like loops already did.

Testing

  • Native taffy layout (direct): root 80x5, child positioning, measure callbacks all correct.
  • JSON import (minimal): import boxes from './data.json'boxes.round.topLeft === '╭'.
  • ink end-to-end: borderless + borderStyle="round", flexDirection row/column, padding/margin/width/justifyContent all lay out via taffy and render positioned ANSI (exit 0).
  • cargo test --bin perry green; cargo build --release green.

Depends on #5024 (function-prototype own-key tracking), already merged.

Known follow-up (separate, minor)

<Text dimColor> renders as [object Object] (boolean style-modifier handling in ink's Text); color= works. Filed separately.

Closes #348.

Ralph Küpper added 2 commits June 12, 2026 14:49
…led-break-from-nested-switch

- perry/yoga native module: taffy-backed flexbox primitives (node tree,
  style setters, measure callbacks via js_native_call_value, computed
  layout) so the real 'yoga-layout' npm package runs natively (no WASM).
  A TS shim ships as the package's index.ts over these primitives.
  ink lays out + renders text/boxes/colors natively through it.
- fix(codegen): a 'break <label>' targeting an outer labeled switch from
  inside a NESTED switch was resolving (via the LabeledBreak fallback) to
  the inner switch's exit instead of the outer's — lower_switch never
  registered its pending label in label_targets (only loops did). This
  miscompiled react-reconciler's element-type classification switch, so
  every Context.Provider became fiber tag 29 (invalid) and its children
  were dropped. Register the pending label on the switch's exit.

Refs #348.
Materialize `import x from "./foo.json"` (with or without
`with { type: "json" }`) as a native module exporting the parsed JSON as
its default export, instead of skipping JSON files outright. JSON is a
syntactic subset of a JS expression, so collect_modules synthesizes
`export default <json>;` and feeds it through the normal parse → lower →
codegen path; non-JSON modules take an identical path. Validates as JSON
first for a clear error on malformed input.

Fixes ink's `<Box borderStyle="round">` crash (cli-boxes imports
`boxes.json`, which previously resolved to the empty-module sentinel and
threw `Cannot read properties of undefined (reading 'topLeft')`).

Bundles the version bump (0.5.1162) + changelog for the native yoga/taffy
backend and the labeled-break-from-nested-switch fix already on this branch.
- cargo fmt on yoga.rs / lib.rs (lint job).
- Add the 14 `perry/yoga` rows to perry-api-manifest so the
  manifest-consistency check (#513) sees a counterpart for every
  NATIVE_MODULE_TABLE dispatch entry (cargo-test job).
- Regenerate docs/src/api/reference.md + docs/api/perry.d.ts to match
  (api-docs-drift job).
@proggeramlug proggeramlug merged commit c3236dd into main Jun 12, 2026
13 checks passed
@proggeramlug proggeramlug deleted the yoga-taffy branch June 12, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compile ink (React-based TUI framework) end-to-end via perry.compilePackages

1 participant