npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@microsoft/webui

v0.0.29

Published

WebUI — high-performance server-side rendering framework. Build-time protocol compiler and streaming renderer.

Readme

@microsoft/webui

High-performance server-side rendering framework. Compiles HTML templates into a binary protocol at build time and renders them with native speed at runtime - no JavaScript runtime overhead.

📖 Full documentation, tutorials, and playground at microsoft.github.io/webui

Installation

npm install @microsoft/webui

The package automatically installs the correct platform-specific native binary for your OS and architecture (Windows, macOS, Linux - x64 and arm64). The Node API requires that native addon and surfaces loading errors directly. It never falls back to a subprocess. Use the webui CLI explicitly for filesystem builds.

AI coding agents

The package includes ai.md, a framework reference matching the installed version. Install the loader skill once:

npx skills add microsoft/webui --skill webui-reference

The loader reads node_modules/@microsoft/webui/ai.md from your application. Package upgrades update the reference without reinstalling the skill. The @microsoft/webui/ai.md subpath also supports package resolution for hoisted dependencies. See AI Coding Agents for migration and setup details.

Quick start

import { build, Protocol } from "@microsoft/webui";

// Build templates into a protocol
const result = build({ appDir: "./src" });

// Decode and index once, then render repeatedly
const protocol = new Protocol(result.protocol, { plugin: "webui" });
const html = protocol.render({ name: "World", items: ["a", "b"] });
console.log(html.toString("utf8"));

API

build(options: BuildOptions): BuildResult

Compiles an application directory of HTML templates into a binary protocol.

const result = build({
  appDir: "./src",        // Path to the template directory
  entry: "index.html",   // Entry file (default: "index.html")
  css: "link",           // CSS strategy: "link", "style", or "module"
  dom: "light",          // Optional: global Light + authored Shadow islands
  cssBundle: true,       // Merge component stylesheets into shared chunks
  plugin: "webui",       // Parser plugin name
  components: [],        // Additional component sources
  componentAssetRoots: ["settings-dialog"], // Static .webui.js asset roots
  metafile: true, // Return an esbuild-compatible asset graph
  cssFileNameTemplate: "[name]-[hash].[ext]", // CSS/component asset filename template
  cssPublicBase: "https://cdn.example.com/assets", // Optional CDN/public href base
  theme: "./themes/brand.json", // Optional design-token theme validation
});

// result.protocol  - Buffer containing the compiled protocol
// result.cssFiles  - Array of [filename, content, ...] pairs
// result.componentAssetFiles - Array of [filename, ESM content, ...] pairs
// result.metafile - Optional esbuild-compatible graph JSON
// result.warnings  - Array of non-fatal build advisory diagnostics
// result.stats     - { durationMs, fragmentCount, componentCount, cssFileCount, protocolSizeBytes, tokenCount }

Unwrapped components default to generated open Shadow roots. Set dom: "light" to render unwrapped components as global Light DOM. A sole bare <template> is also an explicit Light wrapper and is unwrapped; a sole <template shadowrootmode="open"> remains Shadow in either mode. Light CSS uses ordinary selectors; :host, :host-context, and ::slotted fail with unsupported-light-css.

Static component assets use a root/chunk graph. Entry-reachable dependencies remain external, dependencies used by one root stay inline, and dependencies with an identical multi-root consumer set are emitted once as dynamic chunks. Asset-only records are omitted from result.protocol. Component assets cannot be combined with <route>.

When theme is provided, every required CSS token must exist in the theme after local and ancestor custom-property definitions are excluded. A var() usage with a literal fallback (e.g. var(--brand, #000)) is exempt. Missing required tokens fail the build with a structured missing-theme-token error. Misspelled literal-fallback tokens are returned as non-fatal warnings.

Optional state projection

The build-only @microsoft/webui/projection.js subpath exposes the bundler-neutral projection compiler and the supported esbuild adapter. esbuild and TypeScript are optional peer dependencies, so applications that do not use projection do not install or load them:

npm install -D esbuild typescript
import * as esbuild from "esbuild";
import { esbuildProjection } from "@microsoft/webui/projection.js";

await esbuild.build({
  entryPoints: ["src/index.ts"],
  outdir: "dist",
  bundle: true,
  splitting: true,
  format: "esm",
  plugins: [esbuildProjection()],
});

const result = build({
  appDir: "./src",
  plugin: "webui",
  projectionManifests: ["./dist/webui-projection.json"],
});

esbuild runs once and emits both browser chunks and webui-projection.json; WebUI then embeds the exact initial/navigation surfaces into protocol.bin. The adapter uses esbuild's resolved graph and emitted output membership. Source inputs and outputs receive exact hashes; opaque inputs rely on their emitted output proof. Code splitting, dynamic imports, output hashes, and external bundles remain application-owned.

Other bundler adapters can use the exported AdapterContext, compileProjection(), and conformance fixtures without importing esbuild. The package currently ships and supports esbuildProjection() as its official adapter. Custom adapters provide a normalized JavaScript/TypeScript source graph and exact emitted output bytes. Non-source bundler inputs stay outside the semantic graph, so projection does not decode or hash their input bodies.

With no manifest, WebUI performs no JavaScript analysis and preserves full state. Once any manifest is supplied, coverage is strict: every scripted component compiled into the protocol must have exactly one entry. Shared controls built as external bundles should emit their own manifest fragment, then all fragments should be passed through projectionManifests.

Manifest keys are exact JavaScript @observable and @attr property names. During hydration, an existing SSR host attribute wins over projected @attr state. Runtime hosts never load TypeScript, esbuild, or the manifest.

new Protocol(protocol: Buffer, options?: ProtocolOptions)

Decodes and indexes a compiled protocol once. Keep this object for the server lifetime and use it for all runtime operations.

const protocol = new Protocol(protocolBytes, { plugin: "webui" });

Protocol owns its decoded native state. The package does not keep a hidden WeakMap, copy the source Buffer, or expose render functions that accept protocol bytes on every request.

protocol.render(state: object | string, options?: RenderOptions): Buffer

Renders state into a UTF-8 Node.js Buffer, the canonical buffered result for direct HTTP, file, or socket writes:

response.end(protocol.render({ title: "Hello" }));

Call .toString("utf8") explicitly when JavaScript string operations are required.

protocol.prepareState(state) / protocol.renderPrepared(state, options?)

Use an immutable prepared state only when the same snapshot will be rendered repeatedly, such as a cached page shell or fan-out response:

const state = protocol.prepareState({ title: "Cached" });
response.end(protocol.renderPrepared(state));

Preparation performs object serialization and native JSON parsing once. renderPrepared() reuses the parsed native tree and returns bytes identical to render() for the same state and options. The opaque handle is process-local, cannot be serialized, and does not observe later mutations to the source object.

A prepared state retains its native tree until the handle is garbage-collected. This trades resident native memory for avoiding repeated stringify/parse work. Prepare a new snapshot whenever request state changes, and continue to use render() for ordinary per-request state.

protocol.renderStream(state, onChunk, options?): void

Renders with streaming output. Internal handler writes are coalesced around a 16 KiB target before the callback crosses into JavaScript. The callback runs synchronously and its return value is ignored. In particular, response.write() returning false does not pause native rendering, so this API does not provide transport backpressure. A thrown callback error aborts rendering immediately and is rethrown to the caller.

protocol.renderStream(state, (chunk) => {
  response.write(chunk);
});

protocol.streamResponse(options?): StreamingSession

Opens a runtime-discovered progressive session. Unlike renderStream, each session call returns bytes so your server keeps the socket and backpressure.

const session = protocol.streamResponse({
  entry: 'index.html',
  requestPath: '/',
});

res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let step = session.start(initialState);
await write(res, step.bytes);

while (!step.done) {
  const boundary = step.boundary;
  if (boundary) {
    const state = await loadBoundaryState(
      boundary.owner,
      boundary.name,
      boundary.key,
    );
    step = session.resume(boundary.instanceId, state, 'final');
  } else {
    step = session.advance();
  }
  await write(res, step.bytes);
}
res.end();

async function write(res, chunk) {
  if (res.write(chunk)) return;
  // An aborted client never emits 'drain', and surfaces as 'close', not
  // 'error' — so waiting on 'drain' alone would hang forever.
  await new Promise((ok, fail) => {
    const done = (error) => {
      res.off('drain', onDrain);
      res.off('close', onClose);
      if (error) fail(error);
      else ok();
    };
    const onDrain = () => done();
    const onClose = () => done(new Error('client disconnected'));
    res.once('drain', onDrain);
    res.once('close', onClose);
  });
}

| Member | Returns | Description | |--------|---------|-------------| | start(state) | StreamStep | Bytes through the first occurrence or terminal | | resume(instanceId, state, mode?) | StreamStep | Only the pending occurrence's bytes through its checkpoint ('final' | 'updatable') | | advance() | StreamStep | Following parent bytes through the next occurrence or terminal | | update(instanceId, patch) | Buffer | Projected state for a committed updatable occurrence |

StreamStep contains bytes, done, and optional { instanceId, declarationId, owner, name, key }. A descriptor requires resume; no descriptor with done: false requires advance; done: true means complete. resume is boundary-only, while advance carries following parent or tail bytes. No sibling boundary workaround is required. The completed step already contains tail and terminal bytes. Updates are valid between resume and advance, insert no markup, and never rerun hydration. Sessions are single-driver and independent; hold one per request.

inspect(protocol: Buffer): string

Returns a JSON representation of the protocol for debugging.

const json = inspect(protocol);
console.log(JSON.parse(json));

protocol.renderPartial(state, entryId, requestPath, inventoryHex): string

Produces a JSON partial response for client-side navigation, including state, template metadata, condition closures, and route chain.

protocol.renderComponentTemplates(componentTags, inventoryHex): string

Renders templates and styles for on-demand component loading (used by Router.ensureLoaded()). Returns a JSON string with componentStyles, templates, templateFunctions, and inventory. Uses the same inventory bitfield as partial navigation to avoid sending duplicates.

const json = protocol.renderComponentTemplates(["settings-dialog"], inventoryHex);
const { templates, templateFunctions, componentStyles, inventory } = JSON.parse(json);

CLI

The package also includes the webui CLI binary:

# Build templates to disk
npx webui build ./src --out ./dist

# Start a dev server
npx webui serve ./src --state ./data/state.json --port 3000

# Inspect a compiled protocol
npx webui inspect ./dist/protocol.bin

Platform support

| OS | Architecture | Package | |---|---|---| | Windows | x64 | @microsoft/webui-win32-x64 | | Windows | arm64 | @microsoft/webui-win32-arm64 | | macOS | arm64 | @microsoft/webui-darwin-arm64 | | macOS | x64 | @microsoft/webui-darwin-x64 | | Linux | x64 | @microsoft/webui-linux-x64 | | Linux | arm64 | @microsoft/webui-linux-arm64 |

Platform-specific packages are installed automatically as optional dependencies.

License

MIT