惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
雷峰网
雷峰网
量子位
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
博客园 - Franky
罗磊的独立博客
宝玉的分享
宝玉的分享
博客园_首页
腾讯CDC
The GitHub Blog
The GitHub Blog
D
DataBreaches.Net
IT之家
IT之家
D
Docker
Microsoft Security Blog
Microsoft Security Blog
博客园 - 司徒正美
V
V2EX
月光博客
月光博客
N
Netflix TechBlog - Medium
爱范儿
爱范儿
I
InfoQ
P
Proofpoint News Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How a pure-TypeScript flex layout engine closed the last ...
Zhijie Wong · 2026-05-23 · via DEV Community

TL;DR

I've been building Pilates, a flex layout engine for terminal UIs in pure TypeScript. As of last week, across the 9 scenarios in my bench suite, the pure-TS engine is faster than WASM Yoga (the engine Ink uses) on each — including the structural-mutation workload (append + remove a row per frame) Yoga led on by ~5× until phases 15–17 closed it. That flipped to a ~1.7× Pilates win, in pure TypeScript.

No native bindings. No WASM port. The fix was algorithmic, and the algorithmic fix worked in TS.

The numbers

Median latency, win32-x64, Node 22, ~5s tinybench windows with bootstrap CI95:

Scenario Pilates yoga-layout (WASM) Ratio
tiny (10 nodes) 4.5µs 19.0µs 4.2× faster
realistic (~100) 121µs 328µs 2.7× faster
stress (~1000) 601µs 1.94ms 3.2× faster
big (~5000) 3.32ms 9.17ms 2.8× faster
huge (~10000) 8.62ms 18.5ms 2.1× faster
hot-relayout 16.3µs 83.0µs 5.1× faster
hot-relayout + boundaries 15.8µs 77.8µs 4.9× faster
hot-relayout (text mutation) 8.9µs 90.6µs 10× faster
hot-structural 71.3µs 118.3µs 1.7× faster

Caveats up front: 9 hand-picked scenarios, not a universal claim. Reproduce with pnpm bench — about 5 minutes on a recent machine.

Why pure TS can beat WASM here

Terminal UI is a curiously hostile workload for a WASM engine. Trees are small (10–10,000 nodes), but updates are frequent — one keystroke, one tick, one frame. The crossing cost from JS into WASM dominates: Yoga's per-call kernel is a few microseconds, but node.setWidth(N) from JS to WASM is also a few microseconds. A pure-TS engine pays no crossing cost.

That was the thesis going in. Phases 15–17 are evidence the thesis holds even in the worst case — the workload where Yoga's compute kernel is exactly what's being measured, with the tree pre-built and only the structural-mutation layout timed.

How hot-structural went from ~450µs to ~70µs

Two algorithmic changes did the work.

1. Linear-recurrence main-axis positions

The original main-axis position rule was a cumulative sum: each cell's position depended on the size of every prior sibling. A 100-cell row in the stress fixture meant ~300 dependency edges per row.

// Old rule — every cell reads every prior sibling
mainPos[N] = sum(siblings[0..N-1].mainSize + margin + gap)

Enter fullscreen mode Exit fullscreen mode

Replaced with a linear recurrence — each cell only reads the cell immediately before it:

// New rule — each cell only reads the previous one
mainPos[N] = mainPos[N-1] + prev.mainSize + prev.marginEnd + me.marginStart + gap

Enter fullscreen mode Exit fullscreen mode

Reverse-direction (row-reverse / column-reverse) keeps the cumulative-sum fallback because the recurrence depends on the prior cell's already-resolved position, which doesn't hold when iteration is reversed.

2. Fold default-valued style inputs

Observation: roughly half of all input fields in the grammar were sitting at default values forever — margin: 0, minWidth: 0, maxWidth: undefined, etc. They still consumed dirty-flag slots, propagated through dependents, and appeared in dependency sets.

Phase 17 folds these defaults into compile-time constants at grammar-build time. Each per-cell node went from ~15 fields to ~7. The classifier's nodeSig was extended with fold-predicate bits so that mutating from default → non-default correctly triggers a structural rebuild.

Combined, hot-structural went from ~450µs to ~70µs.

Why pure TS over a native rewrite

I considered porting the engine to a native-compiled-to-WASM language before doing the algorithmic work. Glad I didn't.

Yoga's advantage wasn't speed of arithmetic — its C++ kernel is fast and well-tuned, but speed of arithmetic wasn't the bottleneck on this workload. The advantage was the structural-mutation algorithm: Yoga handled it natively, the pure-TS engine was redoing too much work per mutation.

A native-compiled port from my side would have inherited the same algorithmic shape and reached parity at best. The fix was algorithmic, and the algorithmic fix worked in TypeScript. "Pure TS is competitive with native code on this workload" is the actually-interesting result.

Validation, including a same-day hotfix story

  • 1,470 unit + integration tests pass
  • Structural-differential fuzzer green at 3,000 runs
  • 33 Yoga oracle fixtures (cell-for-cell comparison)
  • Byte-identical cached-vs-cold differential mode at 833 runs

A small incident worth mentioning: within hours of publishing 2.0.0, the fast-check property fuzzer caught a real bug — createStyleDirtier was throwing on a node whose entire style had been folded out, a case my analysis said couldn't happen. The fuzzer immediately found it. 2.0.1 shipped same day with the fix and a pinned regression test, and 2.0.0 was deprecated on npm pointing at 2.0.1.

Property-based fuzzing earns its keep. I had been on the fence about whether the fuzzer was worth maintaining; this answered it.

API stability

Public calculateLayout() is byte-identical between 1.x and 2.x. The SemVer-major bump reflects internal API and memory-characteristic shifts:

  • Typed-array runtime (Field.id integer + array storage replacing Map<Field, X>)
  • LayoutPool grows unbounded (tried FinalizationRegistry-based recycling in phase 15C; caused 2× regression so removed)
  • Per-property dirty bitmask replacing single dirty bool
  • Linear recurrence + fold default values (the algorithmic changes above)

If you're using only the documented public API, you upgrade and the speedup is transparent.

Try it

git clone https://github.com/pilatesjs/pilates
cd pilates
pnpm install
pnpm bench   # ~5 min

Enter fullscreen mode Exit fullscreen mode

Or install the engine directly:

npm install @pilates/core

Enter fullscreen mode Exit fullscreen mode

Full React stack (reconciler + widgets):

npm install @pilates/react @pilates/widgets react

Enter fullscreen mode Exit fullscreen mode

Adversarial benchmarks are very welcome — if there's a workload where this approach breaks down, I'd genuinely like to find it. That's the most valuable feedback the project can get right now.