Skip to content

perf(leptos): erase nested views and event callbacks - #4879

Open
skyf0l wants to merge 1 commit into
leptos-rs:leptos_0.9from
skyf0l:erasure-leptos_0.9
Open

skyf0l wants to merge 1 commit into
leptos-rs:leptos_0.9from
skyf0l:erasure-leptos_0.9

Conversation

@skyf0l

@skyf0l skyf0l commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Erase row views and callbacks inside For and ForEnumerate.
  • Erase children inside Suspense, Transition, and ErrorBoundary, and box the ErrorBoundary fallback callback.
  • Erase child views inside Show and ShowLet, and box event callbacks inside on when erase_components is enabled. on_target reuses its existing callback box.
  • Keep these conversions behind erase_components so the normal configuration retains its existing view and callback types.

Motivation

erase_components erases component return types, but nested views and event callbacks remain concrete. My codebase has about 1 million lines of Rust; a source scan found about 3,200 #[component] definitions and 11,200 view! calls, including roughly 450 keyed lists, 3,600 Show/ShowLet uses, 1,500 Suspense/Transition uses, and 3,200 event attributes. Erasing these internal types aims to shorten my development compile loop. Callback boxing adds allocation and dynamic dispatch; the probe below measures compile time, not runtime cost.

Compile time probe

I compiled generated Rust binaries against leptos_0.9 with --cfg erase_components on both sides. Every binary references all its view! modules. The keyed and boundary workloads each contain 24 modules with different view and closure types; the varied workload has 24 distinct structures; the combined workload has 72 modules drawn from keyed, boundary, and effect views. The mixed workload has 24 modules with different HTML layouts, each containing Show, ShowLet, and six event handlers. Its baseline already includes the For and boundary changes, whereas the first four rows compare those changes against their original implementations.

Each comparison used five alternating trials on rustc 1.100.0-nightly, warm dependencies, development code generation, CARGO_INCREMENTAL=0, an empty RUSTC_WRAPPER, and fresh rustc metadata. The figures are median rustc total seconds from -Z time-passes; WebAssembly used the hydrate feature.

Generated workload Erasure measured Original (s) Optimized (s) Change
24 keyed list modules, native SSR For, ForEnumerate 1.211 0.854 -29.5%
24 boundary modules, native SSR Suspense, Transition, ErrorBoundary 1.537 0.927 -39.7%
24 varied view modules, native SSR Keyed and boundary views 1.872 1.213 -35.2%
72 combined modules, native SSR Keyed and boundary views 2.869 1.805 -37.1%
24 mixed modules, native SSR Show, ShowLet, event callbacks 1.599 1.399 -12.5%
24 mixed modules, WebAssembly Show, ShowLet, event callbacks 1.557 1.349 -13.4%

Macro expansion was nearly unchanged in the varied workload (0.044 s original, 0.045 s optimized), so that gain came after expansion. The optimized mixed workload was faster in all five paired runs for each target. These local fixtures are not part of the PR, and the figures exclude dependency rebuilds, bundling, and a whole application build.

Verification

  • cargo fmt --check --package leptos --package tachys passed.
  • A local generated fixture passed SSR rendering and headless Firefox click tests with the optimization enabled and disabled, including typed-target handlers.

The erase_components mode erases component return types, but keyed rows, boundary children, conditional children, and event callbacks still carry concrete types through their internal rendering paths. These types add compiler work for each distinct view structure and handler.

Erase the row views and callbacks used by For and ForEnumerate, the children used by Suspense, Transition, ErrorBoundary, Show, and ShowLet, and the ErrorBoundary fallback callback. Box event callbacks when erase_components is enabled, while avoiding a second box for typed-target handlers. The normal configuration keeps its existing types.

This branch has not been deployed

No deployments
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.

1 participant