Skip to content

Add WASM/JS bindings via wasm-bindgen#927

Draft
caweinshenker wants to merge 2 commits intoDioxusLabs:mainfrom
caweinshenker:main
Draft

Add WASM/JS bindings via wasm-bindgen#927
caweinshenker wants to merge 2 commits intoDioxusLabs:mainfrom
caweinshenker:main

Conversation

@caweinshenker
Copy link

@caweinshenker caweinshenker commented Mar 16, 2026

Objective

Adds JavaScript/TypeScript bindings for Taffy via wasm-bindgen, enabling use of Taffy's layout engine from JS/TS environments (browser and Node.js).

Fixes #928. Relates to #394.

The bindings live in bindings/wasm/ as a separate crate and produce a ~339KB optimized WASM binary.

What's included

  • TaffyLayout class exposing tree creation, style setting, layout computation, and result retrieval
  • Style parsing from plain JS objects with support for:
    • Display: flex, block, grid, none
    • Flexbox: flexDirection, flexWrap, flexGrow/Shrink/Basis, alignItems, justifyContent, etc.
    • CSS Grid: gridTemplateColumns/Rows (with fr/px/% units), gridColumn/Row placement
    • Box model: width/height, min/max variants, margin, padding, border (shorthand + per-side)
    • Position: relative, absolute with top/right/bottom/left insets
    • gap, overflow, aspectRatio
  • Values accept numbers (points), strings ("50%", "auto", "1fr", "100px")

Usage

const { TaffyLayout } = require('taffy-wasm');

const layout = new TaffyLayout();
const child1 = layout.newLeaf({ width: 100, height: 50 });
const child2 = layout.newLeaf({ flexGrow: 1, height: 50 });
const root = layout.newWithChildren(
  { display: 'flex', flexDirection: 'row', width: 400, height: 200, padding: 10, gap: 10 },
  [child1, child2],
);
layout.computeLayout(root, 400, 200);
layout.getLayout(child1); // { x: 10, y: 10, width: 100, height: 50, ... }
layout.getLayout(child2); // { x: 120, y: 10, width: 260, height: 50, ... }

Security review

  • 0 unsafe blocks in the binding code
  • No network I/O, no file system access, no dynamic code execution
  • All computation delegated to Taffy's existing Rust implementation
  • WASM sandbox provides additional isolation
  • 385 lines of pure glue code (JS object → Taffy Style struct translation)
  • Full workspace test suite passes (cargo test --workspace)

AI disclosure

This code was generated with assistance from Claude (Anthropic). The binding architecture follows the same pattern as PR #394 (bindings/wasm/ crate with wasm-bindgen). All code has been reviewed, tested, and verified to compile and produce correct layout results for flexbox, block, and CSS grid layouts.

Context

The bindings are intentionally minimal — they expose the most commonly used CSS properties rather than attempting full coverage. Happy to expand based on feedback.

Tested with wasm-pack build targeting both web and nodejs, verified with flex/block/grid layout tests.

Exposes Taffy's block, flexbox, and CSS grid layout algorithms to
JavaScript/TypeScript via wasm-bindgen. 339KB WASM binary.

API:
  const layout = new TaffyLayout();
  const child = layout.newLeaf({ width: 100, height: 50 });
  const root = layout.newWithChildren(
    { display: 'flex', flexDirection: 'row', gap: 10 },
    [child]
  );
  layout.computeLayout(root, 400, 200);
  layout.getLayout(child); // { x, y, width, height }

Supported CSS properties:
- Display: flex, block, grid, none
- Flexbox: flexDirection, flexWrap, flexGrow/Shrink/Basis, align*, justify*
- Grid: gridTemplateColumns/Rows (fr/px/%), gridColumn/Row placement
- Box model: width/height, min/max, margin, padding, border
- Position: relative, absolute with insets
- Gap, overflow, aspectRatio

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@caweinshenker caweinshenker marked this pull request as draft March 16, 2026 07:33
@caweinshenker
Copy link
Author

Converting to draft — I should have filed the issue (#928) and waited for assignment before submitting. Happy to wait for maintainer feedback before proceeding. Will add tests in the meantime.

9 tests covering flexbox, block, and CSS grid layouts:
- flex_row_basic: horizontal flex with fixed-size children
- flex_row_with_gap_and_padding: padding and gap spacing
- flex_grow: flex-grow fills remaining space
- flex_column: vertical flex stacking
- block_vertical_stacking: block layout with auto height
- block_with_margin: margin spacing in block layout
- grid_2x2_equal: equal 1fr grid cells
- grid_with_gap: grid gap spacing
- percentage_width: percentage-based sizing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@nicoburns nicoburns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks broadly reasonable architectually. It'll be slower than is possible for a couple of reasons:

  • Use of an object-based API and Reflect::get. Yoga's WASM have individual setters instead.
  • Strings for enums. #394 used enums exposed over FFI so that these would be encoded as a number over the WASM boundary.
  • Strings for numeric values (px, %, fr, etc) (this may not be slower, not 100% sure).

However, given how long this feature request has been sitting for, I think it's reasonable to get something that works out and worry about performance later.


A few other notes:

  • #929 adds more robust/complete string parsing based on the cssparser crate to Taffy (which I had coincidentally been working on as part of #926). I would suggest basing this PR on top of that PR and making use of it's string parsing.
  • Relatedly, you may want to make grid-template-columns and similar just one big string (or an option for that). That would allow support for repeat() to work.
  • I notice that "measure functions" are not included in this PR. That might severely restrict the usefulness, as often you'll want to support things like text or images as "leaf nodes". Perhaps we ought to try to include that. #394 had a sketch of this. Can't remember how complete / working it is.

Finally, I would love to see some kind of usage example included in the PR as code. And also some instructions for publishing (would this be published to npm?). If possible for both node.js and web consumers. But if you've only been testing on one platform (web?) then a small runnable example for whatever platform you've been testing on would be fine.

Comment on lines +14 to +15
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't seem to be being used?

Comment on lines +5 to +6
/// Expected values are derived from Chrome DevTools measurements of equivalent
/// HTML/CSS layouts.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true or AI generated?

Comment on lines +325 to +326
"flex-start" | "start" => AlignContent::FlexStart,
"flex-end" | "end" => AlignContent::FlexEnd,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start and end should map to the Start and End variants.

Comment on lines +314 to +315
"flex-start" | "start" => AlignItems::FlexStart,
"flex-end" | "end" => AlignItems::FlexEnd,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start and end should map to the Start and End variants.

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.

WASM/JS bindings via wasm-bindgen

2 participants