Skip to content

Perf tracker: Solid primitives vs the hand-rolled @yak/solid runtime #3389

Description

@ryansolid

DigitecGalaxus/next-yak#644 rewrote the @yak/solid styled() runtime and made it 12× faster on SSR and ~2× on hydrate/mount over its own base. Reading the PR against our code, almost every piece of the rewrite is a reimplementation of a Solid primitive that was too slow or too generic to use directly:

yak hand-rolls instead of because
serializeElement ssrElement needs a finished props object; walks getter-backed merged props again; emits style=""/class="" (#3382)
copyProps / proxyProps merge / omit proxy layers when any source is $PROXY; omit has no predicate/prefix form; plain path leaks $SOURCES (#3384)
createElementRenderer (template + getNextElement + spread + insert) Dynamic / dynamic() per-instance memo for a constant tag; no static class in the template; no runHydrationEvents; tag-only namespace (#3386)
shared withTheme proxy merge(props, { theme }) one merge object per instance is measurable
once() on the server createMemo server memos are real nodes held until the deferred dispose (#3385)

This issue tracks closing those gaps so a library can call Solid and get the same numbers. Baseline harness: ryansolid/yak-bench — the 14 css-in-js-bench workloads, @yak/solid at the PR's base and head, both on 2.0.0-rc.8, with next-yak/React as the reference lane. Acceptance for every item below: a variant of the yak runtime using our primitive lands within noise of the hand-rolled one in that harness.

Where the remaining time goes

Solid 2.0.0-rc.8, @yak/solid at PR head. Full tables: SSR · Chromium.

Static styled intrinsics are fast (SSR 25× React, hydrate/mount 2–3.5× React). The cases still behind React are the ones that go through our generic paths — styled(Component) with {...rest} spreads, dynamic $props, imported primitives:

case SSR PR/React hydrate PR/React mount PR/React
tabs (styled(Component) + spread) 1.10× 0.45× 0.63×
multifile-composition 1.05× 0.46× 0.64×
product-grid (400 tiles × 11 el) 4.15× 0.46× 0.81×
realistic-button 6.10× 0.72× 1.02×
dyn-fair (CSS var per element) 10.06× 0.81× 1.04×
btn-variant vs compose-1 (dynamic vs static props, same element) 2.7M vs 10M inst/s 30.4 vs 15.7 ms 22.6 vs 9.3 ms

Checklist

Not our gap

Definition-time chain flattening, static-class collection, choosing the renderer when the module loads, caching template parts: that is yak doing at runtime what our compiler does at build time. No Solid change removes it.

Landed alongside (correctness surfaced by the same audit)

Versus React (the part that matters more than parity with yak's PR)

Element-dense cases: Solid 1.5–4× faster than React on SSR, hydrate and mount. Component-composition pages (tabs, multifile-composition): React ~4× faster on SSR and ~2× on hydrate. The SSR half is the props-plumbing item above, not Solid's renderer; the hydrate half is being profiled.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions