@hyperfrontend/features§

Eight koi swimming in a single pond, each one rendered by a different framework app

SDK, CLI, and dev server for building, embedding, and orchestrating hyperfrontend micro-frontend features.

What is @hyperfrontend/features?

Embedding another team's app inside your page usually means an iframe, a pile of postMessage conventions nobody wrote down, and a frame that never quite fits the space you gave it. @hyperfrontend/features turns that into a contract: the feature app declares what it sends, what it accepts, and which display modes it supports; the host picks a mode and gets a typed handle back. The messaging protocol underneath is @hyperfrontend/nexus, and this package adds everything around it: iframe management, display modes and sizing, the open/close lifecycle, and a CLI that packages a feature app into an installable shell.

// In the feature app, from '@hyperfrontend/features/hostee'
const feature = createFeature({ name: 'checkout', contract })
await feature.ready()
feature.send('order-placed', { id: 'A-1094' })

// In the host app, from '@hyperfrontend/features/host'
const checkout = createShell({ modes: { dialog: mountDialog }, url: 'https://checkout.example.com' })
checkout.on('order-placed', (order) => showReceipt(order))
checkout.open({ displayMode: DisplayMode.Dialog })

A feature window docks into a slot on a host page; three dots cross the wire between them and it turns solid; the host measures the slot as 720 by 540 and the feature fills it; small beats pulse from the feature to the host once a second; then one order-placed message crosses and a receipt appears on the hostA feature window docks into a slot on a host page; three dots cross the wire between them and it turns solid; the host measures the slot as 720 by 540 and the feature fills it; small beats pulse from the feature to the host once a second; then one order-placed message crosses and a receipt appears on the host

It is organized into independent subpath entry points so consumers import only the surface they need.

Key Features

  • Host SDK

    Embed features with a shell factory, display modes (embedded, dialog, popup, standalone), and open/close lifecycle.

  • Hostee SDK

    Initialize a feature app, declare its contract, and manage its lifecycle.

  • CLI

    init, build, and dev commands driven by feature.config.*, plus serve for production static hosting.

  • Dev server

    Static file server plus a debug UI for inspecting host/hostee message traffic; the same core backs the hf serve production server.

  • Zero-config bundling

    Direct dependencies are bundled by @hyperfrontend/builder, so generated shells stay self-contained.

Why Use @hyperfrontend/features?

You get typed host and hostee SDKs, a CLI, and a dev server for composing micro-frontend features, and it works with any framework (React, Vue, Angular, vanilla JS) and build tool. Features build into self-contained shell packages with their dependencies bundled in, so a host installs one package and inherits no transitive install burden.

Installation

npm install @hyperfrontend/features

Quick Start

In a feature app (the hostee), declare a contract and connect to whatever host embeds it:

import { createFeature } from '@hyperfrontend/features/hostee'

const feature = createFeature({
  name: 'clock',
  contract: { emitted: [{ type: 'tick' }], accepted: [{ type: 'set-timezone' }] },
})

await feature.ready()
feature.on('set-timezone', ({ tz }) => render(tz))
setInterval(() => feature.send('tick', Date.now()), 1000)

In a host app, build a shell and surface the feature in any display mode:

import { builtInDisplayModes, createShell, DisplayMode } from '@hyperfrontend/features/host'

const shell = createShell({
  modes: builtInDisplayModes,
  url: 'https://features.example.com/clock',
  container: '#clock-slot',
  displayMode: DisplayMode.Embedded,
})

shell.on('tick', (time) => console.log('feature said', time))
shell.open()
shell.send('set-timezone', { tz: 'UTC' })

Presentation is host-controlled and contract-preconfigured: a feature declares the display modes it supports (display.modes in feature.config.*, plus per-mode defaults like fixed embedded dimensions or the dialog box footprint and position), the generated shell builds in exactly those modes, and the host picks one per open.

At runtime the SDK measures the host-side space and reports it to the feature as exact pixels (the initial size travels with the mode announcement itself), and frames stay hidden until the session opens. In dialog mode the feature draws its own dialog box inside a transparent full-viewport pane and backdrop/Escape dismissal is coordinated for you. The host SDK docs cover the modes one by one.

Contract actions may carry a required: true flag on accepted entries, which denies the connection unless the counterpart emits that type. Unflagged actions never gate the connection, so adding actions to a contract stays backward compatible.

The SDK's own traffic (the heartbeat, the presentation announcements, dismiss signals, dirty state, and the request/response envelopes) rides the same channel under a reserved __hf: prefix and is filtered out before your handlers run. Your contract must not declare action types beginning with __hf:; everything the plane carries is listed in the architecture guide.

Both sides can opt into a sealed envelope: pass protocol (and, for v4, a sharedKey) to createShell and createFeature, and the two sides negotiate it during the connection handshake.

protocolSession keyssharedKeyDefeats
'none' (default)None: product messages cross in plaintextRejected: passing one throwsNothing
'v3'Agreed fresh over the wire, once per sessionRejected: passing one throwsScripts that listen
'v4'Agreed fresh per session, bound to the pre-shared keyRequired, 16 characters or more: selecting v4 without one throwsScripts that listen, and scripts that could speak to the counterpart

Handshake frames stay plaintext while product messages (including sends queued before the handshake) leave sealed, and a plaintext product message arriving on a secured channel is dropped. Key agreement is paid once per session; each message then costs one AES-GCM operation, so many secured channels can run at once on one page.

Security is fail-closed: a counterpart that cannot run the selected protocol is denied, and a session the counterpart never confirms (a mismatched v4 key, for instance) closes with reason: 'security-unconfirmed' after the connect timeout. A packet the envelope cannot protect or unwrap is discarded and surfaced on that side as an error event shaped { reason: 'security-error', message, code }, so a message one side sent and the other never received is never silent.

A contract may carry a semver version, or createFeature can receive a version option that takes precedence over contract.version. Each side presents its version during the handshake, and incompatible cuts (a different major, or a different minor below 1.0.0) are denied before the channel opens, surfacing as an error on both handles. A side without a version always passes the check, so unversioned peers keep connecting.

A refused handshake ends at once. Whichever gate refuses (contract, policy, version, or fail-closed security), the host destroys the mount and the feature's pending ready() rejects: a deny surfaces as an error carrying the gate's reason, and a cancel the counterpart sent after aborting at its own gates surfaces on the host as error with reason: 'handshake-cancelled'.

Contract entries with a schema are enforced on both ends: send validates the payload against the sender's own emitted schema and throws in the sender's frame before anything crosses the wire, while incoming messages are validated against the receiver's own accepted schema. An invalid payload is dropped and surfaced as an error event shaped { reason: 'invalid-payload', type, errors }. Schema-less actions pass through unchanged.

What each of these controls is actually worth, and which parts of an integration's security remain the operator's job rather than the SDK's, is stated once, in the Security Model.

From the command line, scaffold, build, and serve features with the bundled hf CLI:

# scaffold the hostee glue into an app
npx @hyperfrontend/features init
# generate and bundle a publishable shell package
npx @hyperfrontend/features build --protocol v4
# serve apps with the debug UI
npx @hyperfrontend/features dev
# serve a built site for production
npx @hyperfrontend/features serve --root dist

build requires --protocol v3 or --protocol v4; an explicit --protocol none produces an open, unauthenticated shell and builds only together with --allow-open.

API Overview

Two of the entry points are runtimes, one per side of the frame, and an app imports exactly one. /host gives a host page createShell: hand it a feature URL and a map of display modes, get back a ShellHandle to open, send to, listen on and close. /hostee gives a feature app createFeature: hand it the contract that app will speak, get back a FeatureHandle of the same shape. Both return synchronously; the feature awaits ready(), and the host watches its shell's open, close and error events.

The root entry is the DOM-free one: the contract, config and payload types both runtimes share, the defineConfig helper a feature.config.* file exports, and validateContract for checking one before it ever reaches a wire. Import it from build scripts, config files and Node tests, where reaching for /host or /hostee would drag a browser runtime along.

The last two entry points are Node tooling, importable as modules because the hf bin is only a thin argv wrapper over them. /cli is init, build, dev and serve as functions, for when a shell invocation will not do. /server is the machinery under two of those: startDevServer for the multi-app dev server and its traffic-inspecting debug UI, and startStaticServer for production hosting.

What build emits is the part worth knowing: a feature becomes a self-contained shell package with its direct dependencies bundled in, so a host installs that one package and inherits no transitive install burden. Nx workspaces reach the same tooling as plugin targets, through init and feature generators and build and serve executors under the nx/generators and nx/executors subpaths; nx add @hyperfrontend/features installs the package and runs the init one for you.

Every option, handle, contract and payload type is in the API reference.

Compatibility

Runs on

  • Node.js>=18.0.0supported
  • Browserssupported
  • Web Workerspartially supported

Support is per entry point: /host and /hostee are browser runtimes, /cli, /server, and /generators are Node-only, and the root entry is DOM-free and runs anywhere.

Ships as

  • ESMTree-shakeable
  • CJSNode and older bundlers
  • IIFE3 bundles
  • UMD2 bundles
  • CLIhf

Architecture Highlights§

The package separates the host and hostee surfaces behind independent subpath exports and builds them on top of the Nexus messaging layer. The architecture guide diagrams the host/hostee handshake, the display modes, and how a shell is generated.

API Reference§

View:
Organized by entry point

Module Structure

|

12 modules · 195 total exports

SDK, CLI, and dev server for building and embedding hyperfrontend micro-frontend features.

6 fn27 int18 type2 var

The `hf` command surface as a library: the argv dispatcher behind the bin, the `init`, `build`, `dev` and `serve` runners, the tiered config loader, and the build-config resolver.

9 fn14 int7 var

Pure generators that turn a resolved config + contract into staged output.

4 fn

Host-side SDK for embedding hyperfrontend features (shell factory, display modes, lifecycle).

1 fn18 int12 type6 var

Hostee-side SDK for feature apps (feature initialization and lifecycle).

1 fn6 int3 type1 var

Nx executors entry point: the `build` and `serve` executors plus their option shapes, for programmatic invocation and typed composition.

2 int2 var

Builds a feature's shell package from an Nx target, wrapping the headless `hf build` and reporting a missing rollup native binding as the install command that fixes it.

1 int1 var

Runs the development servers from an Nx target: a long-running executor wrapping the headless `hf dev`, alive until a shutdown signal and closing gracefully on it.

1 int1 var

Nx generators entry point: the `init` and `feature` generators plus their option shapes, for programmatic invocation and typed composition.

2 fn2 int

Scaffolds an existing application into a hyperfrontend feature from an Nx generator, staging every write the headless `hf init` makes through the Nx tree so a dry run touches no disk.

1 fn1 int

Declares the SDK in a consumer workspace's root `package.json`, which is what `nx add` runs after installing it. Repeat runs are no-ops.

1 fn1 int

Serves feature apps: one static server per app, the in-browser debug UI that drives them, and the production static server behind `hf serve`.

23 fn20 int1 type

Browse guides filtered to this packageSuggest a guide