npm.io
3.1.2 • Published 1 week ago

@microsoft/fast-element

Licence
MIT
Version
3.1.2
Deps
0
Size
3.2 MB
Vulns
0
Weekly
0
Stars
9.7K

FAST Element

License: MIT npm version

The fast-element library is a lightweight means to easily build performant, memory-efficient, standards-compliant Web Components. FAST Elements work in every major browser and can be used in combination with any front-end framework or even without a framework.

Installation

From NPM

To install the latest fast-element library using npm:

npm install --save @microsoft/fast-element

Within your JavaScript or TypeScript code, you can then import library APIs like this:

import { FASTElement } from '@microsoft/fast-element';
From CDN

A pre-bundled script that contains all APIs needed to build web components with FAST Element is available on CDN. You can use this script by adding type="module" to the script element and then importing from the CDN.

<!DOCTYPE html>
<html lang="en">
    <head>
        <script type="module">
          import { FASTElement } from "https://cdn.jsdelivr.net/npm/@microsoft/fast-element/dist/fast-element.min.js";

          // your code here
        </script>
    </head>
    <!-- ... -->
</html>

The markup above always references the latest release. When deploying to production, you will want to ship with a specific version. Here's an example of the markup for that:

<script type="module" src="https://cdn.jsdelivr.net/npm/@microsoft/fast-element@1.6.2/dist/fast-element.min.js"></script>

:::note For simplicity, examples throughout the documentation will assume the library has been installed from NPM, but you can always replace the import location with the CDN URL. :::

:::tip Looking for a quick guide on building components? Check out our Cheat Sheet. :::

Browser Requirements

FAST Element v3 assumes a client-side browser Window runtime with native globalThis, Custom Elements, DOM, Shadow DOM, and requestAnimationFrame. The FAST object is now a module-scoped export (not on globalThis). If you need to support an older browser without globalThis, load that polyfill before importing @microsoft/fast-element.

The FAST Element client runtime is not intended to execute in workers, server-side JavaScript runtimes, or other non-window hosts. Use server-side rendering tools to produce HTML for the browser, and run FAST Element in the client window where custom elements connect, render, update, and hydrate.

Debug entrypoint

The declarative attribute fixture covers server-side omission of null/missing attributes and subsequent hydration updates. Explicit empty strings remain present, and omitted attributes retain binding slots. See the server-rendering guide for the rendering contract.

@microsoft/fast-element/debug.js exports enableDebug() instead of configuring FAST at import time. The development root export and debug rollup bundle still enable debug behavior automatically.

import { enableDebug } from "@microsoft/fast-element/debug.js";

enableDebug();

Server-rendered hydration

Call enableHydration() from @microsoft/fast-element/hydration.js before components connect to reuse their server-rendered DOM. Ordered sibling-bounded views use pointer traversal and consume native markers without factory-index arrays; unusual boundaries and potentially mutating cleanup retain the live Range fallback. Nested and adjacent repeats pair only their own server ranges, preserving SSR nodes while reconciling missing or excess items.

Call enableHydration() before components are registered with the platform to also enable pointer-only native marker cleanup for FAST components that do not observe data-fe. Observation tracking is opt-in and excluded from client-only FASTElement bundles. Earlier registrations still hydrate correctly through the conservative fallback, as do unknown registration, observed markers, customized built-ins, and overridden attribute methods. Cleanup remains synchronous.

Existing FAST 3 marker output and opt-in markers_v2 remain supported without application changes. See the server-rendering guide and contributor performance workloads. Contributor-facing internals separate DOM traversal, binding target mapping, and repeat-range discovery; the opt-in benchmark spec is independent of the deterministic correctness and allocation regression spec.

Export Sizes

Bundle sizes for each tree-shakeable export are tracked in SIZES.md and regenerated on every build. See the Export Sizes documentation page for the latest numbers.

Root Imports and Path Exports

The root @microsoft/fast-element entrypoint exports the FAST Element implementation APIs, including the element base class, kernel, controller, definition APIs, template APIs, binding helpers, directives, styles, and schema helpers. The FAST element registry is also available from @microsoft/fast-element/registry.js for focused definition lookups and fastElementRegistry.whenRegistered(tagName) registration promises. Declarative, hydration, context, and dependency injection APIs are available from their focused path exports.

Focused package path exports remain available for consumers that want to import a narrower entrypoint directly. The website's Path Exports page is generated from package.json and lists each supported path.

Dynamic Style Application

Import css, CSSTemplateTag, CSSValue, and style APIs such as ElementStyles, CSSDirective, cssDirective, ComposableStyles, HostBehavior, HostController, StyleStrategy, and StyleTarget from @microsoft/fast-element:

import { ElementStyles, css } from "@microsoft/fast-element";

When runtime state or external signals need to add or remove styles, create the ElementStyles with css and toggle it through this.$fastController.addStyles() / this.$fastController.removeStyles() from the element lifecycle or change handlers.

css templates remain static style definitions. Runtime CSS bindings and behavior-producing CSS directives are no longer supported; keep the condition on the element and toggle a separate ElementStyles instance through the controller when styles need to change.

Declarative HTML

FAST Element publishes its declarative HTML runtime from @microsoft/fast-element/declarative.js. This entrypoint exports the functional APIs for declarative templates: declarativeTemplate(), TemplateParser, and related configuration types. attributeMap() and observerMap() are available from their own focused path exports. The declarative runtime is pure at import time; declarative APIs lazily install only declarative debug messages. Hydration is separate and remains opt-in through enableHydration() from @microsoft/fast-element/hydration.js.

import { FASTElement } from "@microsoft/fast-element";
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";

class MyElement extends FASTElement {}

MyElement.define({
    name: "my-element",
    template: declarativeTemplate(),
});

declarativeTemplate() automatically defines FAST's internal native <f-template> publisher in the relevant registry, resolves the matching <f-template name="my-element">, and keeps the definition template concrete before define() resolves. If multiple matching <f-template> elements are connected, the first connected element supplies the template and later duplicates do not reassign it. Consumers should not import or define the <f-template> implementation directly.

Declarative schema behavior is enabled with define extensions:

import { attributeMap } from "@microsoft/fast-element/attribute-map.js";
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";
import { observerMap } from "@microsoft/fast-element/observer-map.js";

MyElement.define(
    {
        name: "my-element",
        template: declarativeTemplate(),
    },
    [attributeMap(), observerMap()],
);

attributeMap() creates @attr-style accessors for leaf bindings, and observerMap() creates deep observable accessors for discovered root properties. Declarative templates assign definition.schema during template resolution so these extensions always have schema data when used with declarativeTemplate(). For non-declarative/manual schema use, import Schema from @microsoft/fast-element and pass it on the element definition; observerMap() can also receive observerMap({ schema }) directly. When both extensions are present, attribute mapping runs before observer mapping.

Declarative utilities such as deepMerge are available from @microsoft/fast-element/declarative-utilities.js. See docs/declarative/syntax.md for declarative implementation details and the Declarative HTML docs for guides on writing f-templates, defining declarative elements, and server-side rendering. Declarative event bindings use $e for the DOM event object and $c for the execution context.

Prerendered Content Optimization

Hydration of prerendered content is opt-in. Call enableHydration() from @microsoft/fast-element/hydration.js before any FAST elements connect to activate the hydration path:

import { enableHydration } from "@microsoft/fast-element/hydration.js";

const hydration = enableHydration();

await hydration.whenHydrated();
console.log("hydration complete");

By default, hydration handles the initial prerendered batch and then no-ops after the active hydration batch completes. If your app streams Declarative Shadow DOM after the initial batch, keep the hydration hook active. In this mode, enableHydration().whenHydrated() intentionally remains pending because hydration never reaches a global completion point.

import { enableHydration, StopHydration } from "@microsoft/fast-element/hydration.js";

enableHydration({
    stopHydration: StopHydration.never,
});

FAST Element 3.x data-free hydration markers are the only format recognized by default. Server output that still emits the legacy FAST Element 2.x indexed markers will fail to hydrate unless you opt into that reader on its dedicated export path — an interoperability enhancement for backend systems that have not yet adopted the data-free format:

import { enableHydration, markers_v2 } from "@microsoft/fast-element/hydration.js";

enableHydration({ markers: markers_v2 });

Because markers_v2 is only referenced when explicitly imported, its legacy parsing logic is tree-shaken out of the bundle for consumers who don't use it.

When using @microsoft/fast-build, set "markers_v2": true in fast-build.config.json so the renderer and client use the same format.

When hydration is enabled and a FAST element connects with an existing shadow root (from server-side rendering or declarative shadow DOM), ElementController detects this and hydrates instead of re-rendering. Two properties on the controller let you inspect the result:

  • isPrerendered: Promise<boolean> — resolves true when the element had a declarative shadow root (DSD) at connect time, regardless of whether hydration ran.
  • isHydrated: Promise<boolean> — resolves true only when hydration actually ran successfully.

This enables several optimizations:

  • Hydration instead of re-render: The template uses hydrate() to map existing DOM nodes to binding targets rather than cloning new DOM.
  • Declarative template resolution: declarativeTemplate() waits for the matching <f-template> before define() completes, so connected elements hydrate with a concrete template.
  • Attribute skip: onAttributeChangedCallback() skips processing during initial upgrade when the element is prerendered, since server-rendered attribute values are already correct.
  • Binding skip: HTMLBindingDirective.bind() skips updateTarget for attribute and booleanAttribute aspect types when the view is prerendered.
Hydration Mismatch Diagnostics

If a prerendered DOM diverges from the client template in a way FAST cannot reconcile (the render() empty-boundary and repeat() count-mismatch cases recover silently), HydrationBindingError or HydrationTargetElementError is thrown. By default the message is a single line pointing at the opt-in hydrationDebugger(). Install the debugger to get a rich "Expected … / Received …" report with the SSR HTML snippet and structured expected / received fields on the thrown error:

import { enableHydration, hydrationDebugger } from "@microsoft/fast-element/hydration.js";

enableHydration({ debugger: hydrationDebugger() });

The debugger module is tree-shaken out of production hydration bundles when not imported, so the rich diagnostic helpers only land in builds that opt in.

Component authors can await both promises to distinguish prerendered content from successful hydration:

const controller = this.$fastController;
const prerendered = await controller.isPrerendered;
const hydrated = await controller.isHydrated;

if (prerendered && !hydrated) {
    // Had DSD but hydration wasn't enabled — client-side rendered
}

Custom directives can also await controller.isPrerendered and controller.isHydrated (both Promise<boolean> on the ViewController interface) to determine how the view's content was rendered.

Define Extensions

The static define() method on a FASTElement subclass accepts an optional second argument — an array of extension callbacks that are invoked with the resolved element definition before the element is registered with the platform. This enables a plugin pattern where reusable behaviors can hook into element registration.

import { FASTElement, type FASTElementExtension } from "@microsoft/fast-element";

function logger(): FASTElementExtension {
    return definition => {
        console.log(`Defining element: ${definition.name}`);
    };
}

class MyComponent extends FASTElement {
    // component code
}

MyComponent.define({ name: "my-component", template, styles }, [logger()]);

Each extension receives the full FASTElementDefinition, which includes the resolved element name, type, template, styles, and attribute metadata. Extensions run before customElements.define(), so any setup they perform is available when existing DOM elements are upgraded.