microsoft/perf
Speed and memory performance rules for Rust crates, the Node addon, webui-framework, and webui-router.
npx skills add https://github.com/microsoft/webui --skill perf
WebUI's value proposition is speed and low memory usage. Every change to core Rust crates, @microsoft/webui-framework, or @microsoft/webui-router must be evaluated through two lenses: throughput (how fast) and memory (how little).
Server memory is not cheap. Client memory is not unlimited. Every allocation that can be avoided is a win on both sides.
Use this skill when modifying any performance-sensitive code across the stack.
These apply to webui-handler, webui-state, webui-expressions, webui-parser, webui-protocol, webui-ffi, and webui-node.
format!() in writer output. Use sequential writer.write() calls. format! allocates a temporary String every invocation..to_string() on Cow. Write Cow<str> directly to avoid defeating zero-copy.collect::<Vec<_>>() on splits. Iterate path.split('.') directly. Collecting allocates a Vec for sequential access.first_part.len() == path.len() instead of path.contains('.').String::from(ch) in escape loops. Use ch.encode_utf8(&mut buf) with a [u8; 4] stack buffer, or batch contiguous safe chars into a single write.format! in hex parsing. Use direct arithmetic ((hi_nibble << 4) | lo_nibble) instead of u8::from_str_radix(&format!(...)).unwrap_or defaults. If binding_stack.pop() returns None, that's a protocol error - propagate it, don't mask it.#[cold]/#[inline(never)] so they don't inline into hot functions and perturb their code layout. Adding cold error code with no hot-path work has regressed parse benches ~4-5% purely via layout - keep per-element fast-paths inlinable and push the cold fallback out-of-line. See skills/diagnostics/SKILL.md §7.evaluate_with_resolver with a closure. Cloning duplicates the entire JSON tree in memory.String values for read-only access. Use s.as_str() for Value::String branches; only create owned strings for Number/Bool via a scratch buffer or Cow.Vec::with_capacity / String::with_capacity when size is known or estimable. For HTML output, 4096 bytes is a reasonable starting point.&str and slices over owned types. Pass by reference when the callee only reads. Move clone decisions to the caller.Cow<'_, str> when a value is sometimes borrowed, sometimes owned. Avoids unconditional allocation.Arc<T> with clone-on-write or snapshot swapping.@microsoft/webui-framework rulesThese apply to packages/webui-framework (the client-side Web Component runtime).
@observable changes, only bindings referencing that property are visited - not the entire template.cloneNode(true) from cached template fragments. Never use innerHTML for component creation.@event bindings to the bound element, never a shared render root. $wireEvents runs once per block instance, so delegating stacks one listener per block on the same node and fires all of them per dispatch — O(N) for no reduction in listener count.queueMicrotask.<for> block updates use a diff algorithm that only calls insertBefore on nodes that actually moved. Append/prepend/remove are O(1).for..in on objects. Use Object.keys() with an indexed for loop — faster and prototype-safe without needing Object.hasOwn. Applies to setState, setInitialState, and any code iterating user-provided objects.WeakMap-keyed. Parsed template DOMs are cached per metadata object. When metadata is released (e.g., via Router.gc()), the cache entry becomes GC-eligible..filter(), .map(), .slice() in the update hot path. Use index-based iteration.<for> loop item variables use a linked-list scope chain, not cloned Maps or Objects.@microsoft/webui-router rulesThese apply to packages/webui-router (the client-side SPA router).
chain array; the client diffs old vs new and mounts only changed components.for..in on objects. Use Object.keys() with an indexed for loop. for..in walks the prototype chain (slow) and requires an Object.hasOwn guard to be safe — Object.keys is both faster and prototype-safe in one call: // ✗ Bad: slow, prototype-unsafe without guard
for (const key in obj) { ... }
// ✗ Still bad: correct but slower than Object.keys
for (const key in obj) {
if (Object.hasOwn(obj, key)) { ... }
}
// ✓ Good: fast, prototype-safe, no guard needed
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) { ... }
Router.gc() clears cached component templates for routes the user hasn't visited recently. Active route components are never released.cargo bench -p microsoft-webui --bench contact_book_bench # full run
cargo bench -p microsoft-webui --bench contact_book_bench -- --test # quick validation
cargo xtask bench all # all Rust crates
cargo xtask bench node-addon # Node/V8/N-API boundary
Compare Render/1000 P50 before and after. For Node changes, save with
--save-baseline before and compare with --baseline before. Verify output
Bytes is unchanged (same HTML = correct behavior).
window.addEventListener('webui:hydration-complete', () => {
for (const entry of performance.getEntriesByType('measure')) {
if (entry.name.startsWith('webui:hydrate:')) {
console.log(`${entry.name}: ${entry.duration.toFixed(1)}ms`);
}
}
});
When making a performance-related change, report:
Take microsoft/perf from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.