Performance

The layout engine compiles descriptor trees once, reuses text metrics, and caches subtree layouts by width. Repeated relayouts are just arithmetic.

How it works

When you call computeLayout(descriptor, width), the engine does two things:

1. Cold step (first call)

Compiles the descriptor tree — prepares text metrics, resolves padding/margin, and builds reusable node metadata. This is the expensive part.

2. Hot step (subsequent calls)

Reuses the compiled tree and subtree caches keyed by width. Only box-model arithmetic runs — no text re-measurement, no tree re-walking.

This happens automatically. If you pass the same descriptor object twice, the second call reuses the compiled state from the first.

Default path

The simplest API. The engine auto-compiles on the first call and caches for subsequent ones.

ts
import { computeLayout } from "boneyard-js"

const mobile = computeLayout(descriptor, 375)   // cold: compiles + layouts
const desktop = computeLayout(descriptor, 1280)  // warm: reuses compiled

If you only use computeLayout, you don't need to change anything. You already get the caching benefit.

Explicit compiled path

Use compileDescriptor() when you want to control exactly when the cold step happens.

ts
import { compileDescriptor, computeLayout } from "boneyard-js"

const compiled = compileDescriptor(descriptor)

const mobile  = computeLayout(compiled, 375)   // hot
const tablet  = computeLayout(compiled, 768)   // hot
const desktop = computeLayout(compiled, 1280)  // hot

This is useful for:

  • SSR rendering at multiple breakpoints
  • Descriptor registries loaded once at startup
  • Animation loops or responsive tools that relayout often
  • Benchmarking cold vs warm cost separately
Mutation detection

If you mutate a descriptor object in place, the engine detects the change and rebuilds the compiled state automatically on the next layout call.

ts
descriptor.children[0].text = "Updated title"

// Automatically detects the mutation and recompiles
const result = computeLayout(descriptor, 375)

You can also force a rebuild immediately with invalidateDescriptor():

ts
import { invalidateDescriptor } from "boneyard-js"

invalidateDescriptor(descriptor)  // clears cached compiled state
Benchmarks

Measured with the built-in benchmark runner (pnpm --dir packages/boneyard benchmark).

CaseColdWarmCompiledSpeedup
text-leaf0.041 ms0.0004 ms0.0004 ms~105x
dashboard-card0.096 ms0.006 ms0.006 ms~16x

Cold = brand new descriptor each call. Warm/compiled = same object, cache populated. Run your own benchmarks with pnpm --dir packages/boneyard benchmark.