@hyperfrontend/ builder§
Composable, vendor-neutral build toolkit for TypeScript libraries, JS bins, and Node SEA native binaries.
What is @hyperfrontend/builder?
@hyperfrontend/builder is a build-time Node.js toolkit that turns a TypeScript
source tree into a publishable npm package. From a single declarative config it
discovers entry points, resolves externals, bundles each entry in isolation,
emits type declarations, synthesizes the output package.json, copies assets,
and, optionally, produces JavaScript bins and standalone Node SEA native
binaries.
It is vendor-neutral: nothing about a consumer's workspace (package naming, which deps are first-party, hoist policy) is hard-coded. You inject those opinions through predicates and config, so the same toolkit drives a leaf utility library and a multi-entry framework alike.
Key Features
emit ESM, CJS, IIFE, and UMD bundles from one config; omit a format to skip it.
synthesize JavaScript bins and cross-platform Node SEA native executables.
each entry point bundles independently, keeping peak memory bounded on large graphs.
Predicate-driven extensibility
classify workspace packages, externals, and assets with plain functions instead of config DSLs.
bundle first-party and third-party dependencies, with an additive post-emit pass that dedups shared internals into
_shared/chunks.run the bundle, package, and bin phases together via
build, or drive each phase on its own.
Why Use @hyperfrontend/builder?
Most library bundlers assume one entry point, one format, and a fixed notion of
what is "external." @hyperfrontend/builder is built for monorepos that publish
many packages with shared internals and varied output needs:
- You need ESM and CJS and CDN-ready bundles from the same source.
- You ship CLIs and want native binaries without standing up a separate SEA pipeline.
- You want bundled, self-contained packages without forcing transitive installs on consumers.
- You want to script the build programmatically (or hand it to the
hf-buildCLI) without adopting a heavyweight, opinionated framework.
Installation
npm install --save-dev @hyperfrontend/builder
typescript is a regular dependency of the builder, not a peer: installing the
builder installs a compiler, and the published manifest declares no
peerDependencies at all. Declaration emit spawns the workspace's own
node_modules/.bin/tsc, so when your project already depends on TypeScript that
is the compiler that runs. The builder is built against TypeScript >= 5.9.
Quick Start
Drive the full pipeline programmatically with build:
import { build, byPrefix } from '@hyperfrontend/builder'
const result = await build({
projectRoot: '/abs/path/to/libs/my-lib',
workspaceRoot: '/abs/path/to/workspace',
// Treat sibling workspace packages as first-party (bundled), everything else external.
isWorkspacePackage: byPrefix('@my-scope/'),
esm: { bundleWorkspaceDeps: true },
cjs: { bundleWorkspaceDeps: true },
})
console.log(result)
Or build straight from a JSON config with the bundled CLI:
# Reads ./builder.config.json by default
hf-build --config ./builder.config.json --verbose
Need finer control? Compose the phases yourself:
import { createBuildContext, runBundlePhase, runPackagePhase } from '@hyperfrontend/builder'
const ctx = createBuildContext(config)
await runBundlePhase(ctx, config)
await runPackagePhase(ctx, config, /* formats */ [])
API Overview
The surface is the pipeline, in order. build(config) is the whole of it: it derives a BuildContext, runs the bundle, package and bin phases against it, and resolves
to a BuildResult carrying per-format counts, the artifacts emitted and a wall-clock duration. Each phase stays callable on its own against a context you built
yourself, so runBundlePhase,
runPackagePhase and
runBinPhase are the seam for driving one step in isolation.
Most of that work is discovery rather than declaration, which is why the config stays small. Entry points come from the folder layout:
discoverEntries walks src/, and every directory holding an index.ts
becomes a published subpath, so adding an entry point is adding a folder. The seams that could have hard-coded a workspace are plain predicate functions instead:
isWorkspacePackage is a (name: string) => boolean, with
byPrefix and
byNames as conveniences for the two common answers and a closure of your own just as
valid an argument.
What ships is measured rather than predicted. Each entry bundles in its own spawned child process, one per entry per format, and that isolation is what keeps peak
memory flat instead of climbing with the size of the graph; declarations are not synthesized in-process at all, since the builder spawns the workspace's own tsc and
flattens what it emits. The output package.json is reflected from what actually landed:
synthesizePackageJson writes exports, main, module and types
from the formats that really emitted, and
reflectFilesAllowlist walks the finished output tree for files.
The sub-path entries expose that same machinery a level down, each for a different job: /bundle and its children for entry discovery, externals, rollup dispatch,
declarations and the shared-internals dedup pass; /package for the manifest, assets and third-party licenses; /bin for JavaScript bins and Node SEA binaries;
/memory for the build-memory monitor; /presets for the predicate factories; and /models for the types all of them speak. Import the root when you want the
pipeline, a sub-path when you are replacing one step of it.
Every config field, phase signature and result type is in the API reference.
Compatibility
Runs on
- Node.js>=18.0.0supported
- Browsersnot supported
- Web Workersnot supported
Ships as
- ESMTree-shakeable
- CJSNode and older bundlers
- CLIhf-build, plus native builds
Architecture Highlights§
buildorchestrates; phases compose.build(config)runs the full pipeline, whilerunBundlePhase,runPackagePhase, andrunBinPhaseremain individually callable against a sharedBuildContextfromcreateBuildContext.- Predicate extension model. Externals, workspace membership, and asset conditions are expressed as predicates (
byNames,byPrefix, or your own), keeping the core free of workspace-specific assumptions. - Memory-aware by design. Per-entry bundling plus an opt-in memory monitor (
createMemoryMonitor,recover) keep large builds inside constrained environments.
The architecture guide covers the phase pipeline, the per-entry worker model, and the shared-internals dedup pass.
API Reference§
Module Structure
21 modules · 247 total exports
Composable, vendor-neutral build toolkit for TypeScript libraries, JS bins, and Node SEA native binaries.
ƒ Functions
package.json#files from the materialized output tree. Returns a BuildResult summarizing what was emitted and how long it took. A failure in any phase still flushes the memory-monitor summary (when enabled) before re-throwing the original error.
Parameters
| Name | Type | Description |
|---|---|---|
§config | BuildConfig | Top-level builder configuration. |
Returns
Promise< BuildResult>BuildResult with per-format counts, raw format outputs, bin outputs, and total duration in milliseconds.Example
Building a library with workspace-aware externals
import { build } from '@hyperfrontend/builder'
import { byPrefix } from '@hyperfrontend/builder/presets'
const result = await build({
projectRoot: '/abs/libs/foo',
workspaceRoot: '/abs/repo',
isWorkspacePackage: byPrefix('@hyperfrontend/'),
esm: { bundleWorkspaceDeps: false },
cjs: { bundleWorkspaceDeps: false },
})
result.formatCounts.esm // => number of ESM entries emittedUse this preset when the workspace exposes packages under heterogeneous scopes (or unscoped names) and a single string prefix can't capture them all.
Parameters
| Name | Type | Description |
|---|---|---|
§names | string[ | Exact package names to treat as workspace-internal. |
Returns
IsWorkspacePackagePredicatetrue when the supplied name appears in names.Example
Tagging an explicit set of workspace packages
const isWorkspacePackage = byNames(['@hyperfrontend/logging', 'internal-utils'])
isWorkspacePackage('internal-utils') // => true
isWorkspacePackage('rollup') // => falseThe scope is treated as a literal string prefix: pass the full scope including the trailing slash (
'@hyperfrontend/') when matching scoped packages so the predicate doesn't accidentally treat @hyperfrontend-foo/x as a workspace package.Parameters
| Name | Type | Description |
|---|---|---|
§scope | string | Literal prefix to match against package names. |
Returns
IsWorkspacePackagePredicatetrue when the supplied name starts with scope.Example
Matching every workspace package by scope
const isWorkspacePackage = byPrefix('@hyperfrontend/')
isWorkspacePackage('@hyperfrontend/logging') // => true
isWorkspacePackage('rollup') // => falseDefaults applied:
outputPath→<workspaceRoot>/dist/<projectRelativePath>tsConfigPath→<projectRoot>/tsconfig.lib.jsonexternal→[]assets→[]isWorkspacePackage→ predicate that always returnsfalse
<projectRoot>/src exactly once during context creation.Parameters
| Name | Type | Description |
|---|---|---|
§config | BuildConfig | Top-level builder configuration. |
Returns
BuildContextExample
Building a context manually for a custom orchestrator
const context = createBuildContext({ projectRoot, workspaceRoot })
await runBundlePhase(context, config)process.memoryUsage(). The monitor records snapshot history, emits threshold warnings when
check() is called, and is intended for instrumenting long-running build phases. All thresholds default to safe values when omitted.Parameters
| Name | Type | Description |
|---|---|---|
§options | MemoryMonitorOptions | Optional threshold overrides. Each field defaults to a value appropriate for builder workloads (warning 512 MB, critical 768 MB, growth 50 MB).(default: {}) |
Returns
MemoryMonitorMemoryMonitor with snapshot, check, logDebug, logSummary, and getSnapshots methods.Example
Recording snapshots between phases
const monitor = createMemoryMonitor({ warningMB: 256, criticalMB: 512, growthMB: 32 })
monitor.check('bundle:start')
await runBundlePhase(ctx, config)
monitor.check('bundle:end')
monitor.logSummary()globalThis.gc is available (Node.js started with --expose-gc). This is the always-on free utility companion to the opt-in memory monitor. Call it between memory-heavy phases to drain pending I/O microtasks and reclaim transient allocations before the next phase begins.
Returns
Promise< void>Example
Yielding between heavy build phases
await runBundlePhase(ctx, config)
await recover()
await runPackagePhase(ctx, config)bin.sea. Returns the flattened list of every output produced. A bin that declares a
sea block must also produce a CJS output (format: 'cjs' or include 'cjs' in the list); the CJS artifact becomes the SEA main script, and a bin without one throws. Native emission is skipped silently with an info log when the current host doesn't match any declared platform: CI orchestrates the matrix so each declared platform is built on the matching runner.Parameters
| Name | Type | Description |
|---|---|---|
§ctx | BuildContext | Resolved build context. |
§bins | BinConfig[ | Bin declarations to synthesize. Pass an empty array (or omit config.bin from the facade) to skip the phase entirely. |
Example
Running the bin phase from a custom orchestrator
const binOutputs = await runBinPhase(context, config.bin ?? [])runBundlePhase( context: BuildContext, config: BuildConfig, monitor?: MemoryMonitor): Promise< FormatOutputs>
Iterates the format-specific configurations in
config, resolves the matching entry points for each format, and bundles every one. After all bundles are written, emits .d.ts declarations for the project exactly once.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
§config | BuildConfig | Top-level builder configuration. Only the format and tsConfig fields are consulted by this phase. |
§monitor? | MemoryMonitor | Optional memory monitor; when provided, peak heap inside the bundle phase is sampled at each format and declaration step. |
Returns
Promise< FormatOutputs>Example
Driving the bundle phase from a custom orchestrator
const formatOutputs = await runBundlePhase(context, config)runPackagePhase( ctx: BuildContext, config: BuildConfig, formatOutputs: FormatOutputs): Promise< void>
package.json, materializes any configured asset specs, and (when config.thirdPartyLicenses is enabled, defaulting to true for builds with at least one bundled dep) emits THIRD_PARTY_LICENSES.md. The phase consumes:
- the source
package.json(read fresh fromctx.projectRoot) - the resolved BuildContext (output path, workspace predicate, assets, discovery)
- the FormatOutputs aggregated by the bundle phase
dependencies map only when both config.filterWorkspaceDepsFromOutput and ctx.isWorkspacePackage are present. Inheritance and CDN overrides are forwarded verbatim to synthesizePackageJson.Parameters
| Name | Type | Description |
|---|---|---|
§ctx | BuildContext | Resolved build context. |
§config | BuildConfig | Top-level builder configuration. Reads inheritFieldsFrom, filterWorkspaceDepsFromOutput, unpkg, jsdelivr, bin, and thirdPartyLicenses. The files allowlist is owned by finalizeFilesAllowlist, not this phase. |
§formatOutputs | FormatOutputs | Outputs collected during the bundle phase. |
Example
Driving the package phase from a custom orchestrator
await runPackagePhase(context, config, formatOutputs)◈ Interfaces
Either
files or glob selects the inputs under from. When neither is provided, every file directly under from is copied.Properties
The source file is fixed at
src/bin/<name>.ts. The runner export defaults to the file's default export; override with runner to target a named export instead.Properties
Properties
kind:"cjs" | "esm" | "native"Output kind: a JS script for one of the supported formats, or a native SEA binary.build() facade.Properties
files?:string[ ]Override the published package.json#files allowlist. When omitted, the allowlist is reflected from the materialized output tree after every emit phase: it names exactly what shipped. Provide an explicit array to take full control of what
npm publish ships.filterWorkspaceDepsFromOutput?:booleanDrop workspace-internal entries from the output package.json's dependencies.inheritFieldsFrom?:InheritFromSpecSelectively copy fields from another package.json onto the output package.json.isWorkspacePackage?:IsWorkspacePackagePredicateWorkspace-package predicate; when omitted the bundler treats every dep as external.memoryMonitor?:boolean | MemoryMonitorOptionsEnable the memory monitor; pass true for defaults or an options object for custom thresholds.outputPath?:stringAbsolute output directory. Defaults to <workspaceRoot>/dist/<projectRelativePath>.tsConfig?:stringPath to the project's tsconfig used for declarations. Defaults to <projectRoot>/tsconfig.lib.json.verbose?:booleanRaise the build's log level. When true, the shared logger emits at debug (surfacing every phase's progress, timing, and memory diagnostics); when omitted or false, the build stays quiet at error. Also settable via the hf-build --verbose flag.workspaceDepPolicy?:Record< string, WorkspaceDepHoistPolicy>Per-package override of the workspace-dependency hoist policy, keyed by package name. Packages absent from the map default to 'sub-path' (granular, zero-config); set a package to 'whole-surface' to opt into collapsing its sub-paths onto the root chunk. Builder ships no built-in entries; consumers inject their own opinions here, mirroring isWorkspacePackage.BuildConfig. Unlike
BuildConfig, every path and option here has been resolved to an absolute value and every default has been filled in. Builder primitives consume the BuildContext, never the raw config.Properties
bundledDeps:string[ ]Third-party deps bundled into _dependencies/<dep>/ and stripped from the output package.json. Empty unless at least one format declares bundleAllDeps.isWorkspacePackage:IsWorkspacePackagePredicateWorkspace-package predicate, normalized to always-defined.startedAt:numberWall-clock timestamp captured at context creation, used for BuildResult.durationMs.workspaceBundledDeps:WorkspaceBundledDep[ ]Workspace deps bundled into _dependencies/<packageName>(/<sub>)?/. Empty unless at least one format declares bundleAllDeps and the project declares workspace deps.build() facade.Properties
The default set is
package.json#dependencies minus peerDependencies minus any package matching isWorkspacePackage. include adds packages absent from dependencies; exclude skips ones that would otherwise be picked up. Neither override can resurrect a peer or workspace package.Properties
Properties
bundleAllDeps?:boolean | BundleAllDepsOptionsBundle every third-party dep into _dependencies/<dep>/ and route entry imports through that directory at install-relative paths. When true, builder produces a fully self-contained dist with no dependencies field on the published package.bundleWorkspaceDeps?:booleanInline workspace dependencies (true) or keep them external (false). Defaults to true.entry?:string | string[ ]Entry pattern(s): exact path, glob, or list. Omit to include all detected entries.exports field. Each property maps an export condition (e.g.,
import, require, types) to a resolved file path, or to a nested conditional-export object for further refinement.Properties
Properties
Properties
bundleAllDeps?:boolean | BundleAllDepsOptionsBundle every third-party dep into _dependencies/<dep>/ and route entry imports through that directory at install-relative paths. When true, builder produces a fully self-contained dist with no dependencies field on the published package.bundleWorkspaceDeps?:booleanInline workspace dependencies (true) or keep them external (false). Defaults to true.entry?:string | string[ ]Entry pattern(s): exact path, glob, or list. Omit to include all detected entries.FormatOutputs.Properties
Properties
Properties
Properties
Properties
createMemoryMonitor.Properties
check:( label: string) => MemorySnapshotCapture a snapshot and emit warnings when configured thresholds are crossed.logDebug:( label: string) => MemorySnapshotCapture a snapshot and emit a debug-level log line summarizing it.snapshot:( label: string) => MemorySnapshotCapture a snapshot, append it to the history, and return it.Properties
Sizes are normalized to megabytes (1 MB = 1024 * 1024 bytes) so callers can compare directly against the configured thresholds.
Properties
Only fields the builder pipeline interacts with directly are typed; arbitrary additional fields are preserved through the index signature and emitted to the output package.json.
Properties
Properties
Properties
Properties
_dependencies/<packageName>(/<sub>)?. The default set is
package.json#dependencies intersected with the workspace predicate.◆ Types
type AssetConditionPredicate = ( pkg: PackageJson) => booleantype BinFormatSpec = BinScriptFormat | BinScriptFormat[ ]type BinScriptFormat = "cjs" | "esm"root: single entry atsrc/index.tsplatform: browser / node split undersrc/browser/andsrc/node/feature: multiple feature modules undersrc/<feature>/hybrid: a mix of root, platform, and / or feature entriescomplex: nested platform-plus-feature structures
type EntryPointCategory = "root" | "platform" | "feature" | "hybrid" | "complex"type EntryPointPlatform = "browser" | "node"exports map entry: a resolved string path, a conditional exports object, or a nested record for sub-conditions.type ExportValue = string | ConditionalExport | Record< string, unknown>Returning
true opts the package into workspace-aware behavior such as inlining during bundling or stripping from the published dependencies map.type IsWorkspacePackagePredicate = ( name: string) => boolean<process.platform>-<process.arch>.type SeaPlatform = "linux-x64" | "linux-arm64" | "darwin-x64" | "darwin-arm64" | "win32-x64"'sub-path' (the zero-config default) gives every public tsconfig specifier of the dep (root and each sub-path) its own _dependencies/<name>(/<sub>)?/index.<ext> chunk, preserving sub-module tree-shaking and reuse, and supporting subpath-only packages that expose no root export. 'whole-surface' is an explicit opt-in collapse: it routes every import of the dep onto a single root chunk, and therefore requires the dep to expose a root export.type WorkspaceDepHoistPolicy = "sub-path" | "whole-surface"JS bin synthesis, composed via runBinPhase.
ƒ Functions
bin.sea. Returns the flattened list of every output produced. A bin that declares a
sea block must also produce a CJS output (format: 'cjs' or include 'cjs' in the list); the CJS artifact becomes the SEA main script, and a bin without one throws. Native emission is skipped silently with an info log when the current host doesn't match any declared platform: CI orchestrates the matrix so each declared platform is built on the matching runner.Parameters
| Name | Type | Description |
|---|---|---|
§ctx | BuildContext | Resolved build context. |
§bins | BinConfig[ | Bin declarations to synthesize. Pass an empty array (or omit config.bin from the facade) to skip the phase entirely. |
Example
Running the bin phase from a custom orchestrator
const binOutputs = await runBinPhase(context, config.bin ?? [])Node SEA native binary primitives: config generation, blob prep, host resolution, postject injection, and macOS code-sign cleanup.
ƒ Functions
Defaults to
--sign - (ad-hoc signing), which is sufficient for local execution and satisfies macOS Catalina+ launch requirements without an Apple Developer identity. Release tooling can pass a real identity via inputs.identity.Parameters
| Name | Type | Description |
|---|---|---|
§inputs | ApplyCodesignInputs | Binary path and optional signing identity. |
Returns
CodesignResultExample
Ad-hoc signing the produced SEA binary on macOS
applyCodesign({ binary: '/abs/dist/libs/builder/bin/hf-build.darwin-arm64' })Pipeline (current-platform-only; cross-platform matrices are orchestrated externally):
- Validate the bin declares CJS: SEA requires a CJS bundle as the embedded script.
- Skip silently with an info log if the current host doesn't match any declared platform.
- Generate the SEA config JSON and write it to disk.
- Spawn
node --experimental-sea-config <path>to emit the SEA preparation blob. - Resolve the Node host binary for the current platform (defaults to
process.execPath). - Dispatch a forked inject worker that clones the host, embeds the blob via
- On macOS, strip the signature the injection invalidated so the unsigned binary still runs.
- Delete the SEA build intermediates (config JSON + prep blob) so only the
Native binaries are not auto-wired into
package.json#bin: they are shipped as separate release artifacts.Parameters
| Name | Type | Description |
|---|---|---|
§inputs | BuildNativeBinInputs | Bin declaration, resolved context, and the path to the already-built CJS bundle. |
Returns
Example
Producing the SEA binary for the current runner
const outputs = await buildNativeBin({
bin: { name: 'hf-build', format: 'cjs', sea: { platforms: ['linux-x64'] } },
ctx: context,
cjsOutputPath: '/abs/dist/libs/builder/bin/hf-build.js',
})true when the current Node process matches one of the declared SEA target platforms: i.e., when <process.platform>-<process.arch> is present in declaredPlatforms. Builder uses this gate to skip native emission silently on runners that weren't asked to produce a binary for the current host. CI orchestrates the matrix so each declared platform is built on the matching runner.
Parameters
| Name | Type | Description |
|---|---|---|
§declaredPlatforms | unknown | Platforms declared on the bin's sea config. |
Returns
booleanExample
Gating native emission to declared targets
if (!currentPlatformMatches(bin.sea?.platforms ?? [])) {
log.info(`skipping native build for ${bin.name} on ${currentPlatformTarget()}`)
return []
}<process.platform>-<process.arch> (e.g., linux-x64). The string is shaped to match SeaPlatform but is returned as a plain string because callers commonly compare against arbitrary user-declared platform lists, which may legally include values not in the supported union.
Returns
string<platform>-<arch> for the current process.Example
Identifying the current target
const target = currentPlatformTarget() // 'linux-x64'dispatchInjectWorker( job: InjectWorkerJob, options: DispatchInjectWorkerOptions): Promise< InjectWorkerReport>
reportPath is overwritten with a temp-dir path created by this function. The worker writes a JSON report at the temp path; this function reads it after the child exits and returns the per-job statistics. If the worker exits non-zero or fails to produce a report, the function throws with a label-rich message.
The temp report directory is cleaned up before returning, regardless of success or failure.
Parameters
| Name | Type | Description |
|---|---|---|
§job | InjectWorkerJob | Inject job to dispatch. |
§options | DispatchInjectWorkerOptions | Worker path + optional memory monitor. |
Returns
Promise< InjectWorkerReport>Example
Dispatching a SEA inject for hf-build
const report = await dispatchInjectWorker(
{ hostBinary, outputBinary, blobPath, resourceName, machoSegmentName, sentinelFuse, reportPath: '' },
{ workerPath: '/abs/dist/.../worker.cjs.js', label: 'hf-build' }
)node --experimental-sea-config <seaConfigPath> to produce the SEA preparation blob declared by the config's output field. Spawn errors and non-zero exit codes are surfaced as thrown
Errors with the captured stderr included; this function does not retry.Parameters
| Name | Type | Description |
|---|---|---|
§inputs | GenerateSeaBlobInputs | Resolved SEA config path + the blob path declared in that config. |
Returns
GenerateSeaBlobResultExample
Generating the SEA prep blob
const result = generateSeaBlob({
seaConfigPath: '/abs/dist/libs/builder/bin/hf-build.sea-config.json',
outputBlobPath: '/abs/dist/libs/builder/bin/hf-build.sea-prep.blob',
})node --experimental-sea-config <path>. Builder always emits
disableExperimentalSEAWarning: true to silence the runtime warning banner on every native binary invocation: published bins are expected to behave like first-class executables.Parameters
| Name | Type | Description |
|---|---|---|
§inputs | SeaConfigInputs | Resolved absolute paths for the SEA main script and output blob. |
Returns
SeaConfigDocumentExample
Generating a SEA config for a bin
const config = generateSeaConfig({
mainPath: '/abs/dist/libs/builder/bin/hf-build.cjs.js',
outputPath: '/abs/dist/libs/builder/bin/hf-build.sea-prep.blob',
})outputBinary and injects the SEA preparation blob via [postject](https://www.npmjs.com/package/postject)'s programmatic API. The host binary itself is never modified: postject mutates the cloned copy at
outputBinary. The injection uses Node's expected SEA resource name and fuse so the produced executable is recognized by Node's SEA runtime. Callers are responsible for adjusting code-signing afterwards (see removeCodesign for macOS).
Parameters
| Name | Type | Description |
|---|---|---|
§inputs | InjectBlobInputs | Host binary, target output path, and blob path. |
Example
Producing a SEA binary on a Linux runner
await injectBlob({
hostBinary: process.execPath,
outputBinary: '/abs/dist/libs/builder/bin/hf-build.linux-x64',
blobPath: '/abs/dist/libs/builder/bin/hf-build.sea-prep.blob',
})inputs.binary (via codesign --remove-signature) after postject's blob injection has invalidated it, leaving an unsigned binary that runs locally. No-op on non-macOS hosts. Re-signing the produced binary (with a real Apple Developer ID) is left to release tooling; this step only produces an unsigned executable that runs locally.
Parameters
| Name | Type | Description |
|---|---|---|
§inputs | RemoveCodesignInputs | Path to the binary to clean up. |
Returns
CodesignResultExample
Removing the signature on macOS before postject inject
removeCodesign({ binary: '/abs/dist/libs/builder/bin/hf-build.darwin-arm64' })@swc-node/register (bootstrap case where builder is building itself for the first time and the dist worker doesn't exist yet). Looks at, in order:
<workspaceRoot>/dist/libs/builder/bin/native/worker/index.cjs.js<workspaceRoot>/node_modules/@hyperfrontend/builder/bin/native/worker/index.cjs.js<workspaceRoot>/libs/builder/src/bin/native/worker/index.ts(with--require \@swc-node/register)
Parameters
| Name | Type | Description |
|---|---|---|
§workspaceRoot | string | Absolute workspace root. |
Returns
InjectWorkerInvocationundefined if no candidate exists.Example
Locating the worker for an in-workspace consumer
const invocation = resolveDefaultInjectWorkerPath('/abs/repo')
if (!invocation) throw new Error('builder inject worker artifact not found')The resolver is current-platform-only: the host binary is always
process.execPath. Cross-platform builds are orchestrated by an external CI matrix where each runner produces the binary for its own platform. Calling this function with a target platform that doesn't match the current host throws: the caller is expected to gate via currentPlatformMatches before invoking the SEA pipeline. The throw exists as a defense-in-depth check should that gate be bypassed.
Parameters
| Name | Type | Description |
|---|---|---|
§inputs | ResolveHostBinaryInputs | Target platform and optional current-host overrides for testability. |
Returns
stringExample
Resolving the host for the current runner
const host = resolveHostBinary({ platform: 'linux-x64' }) // process.execPath◈ Interfaces
Properties
Properties
cjsOutputPath:stringAbsolute path to the CJS bundle the SEA blob will execute. Must already be written to disk.Properties
Properties
Properties
blobPath:stringAbsolute path to the SEA preparation blob produced by node --experimental-sea-config.machoSegmentName?:stringMach-O segment name for macOS injection. Defaults to Node's expected NODE_SEA.resourceName?:stringResource name for the injected SEA section. Defaults to Node's expected NODE_SEA_BLOB.sentinelFuse?:stringSEA fuse string searched for in the binary. Defaults to Node's standard SEA fuse.--require \@swc-node/register when the worker is loaded from TypeScript source during a bootstrap build).Properties
JSON.stringify / JSON.parse: no functions, no class instances.reportPath when the worker exits cleanly. Captured from the worker process: the parent never observes the ~138 MB postject buffer because the entire load lives and dies in the child.
Properties
Properties
node --experimental-sea-config. Mirrors the [Node SEA configuration schema] (https://nodejs.org/api/single-executable-applications.html). Builder always sets
disableExperimentalSEAWarning so the produced binary doesn't print the warning banner on every invocation.Properties
Properties
● Variables
0 → 1 to mark a SEA-injected binary. Built at runtime from two parts so the contiguous sentinel literal never appears in the bundled JS of a bin that imports this module: that JS is embedded in the SEA blob, and a contiguous copy there collides with the host binary's own sentinel, making postject reject the inject with "Multiple occurences of sentinel". Keep it split; do not inline into a single string literal.
Forked-worker entry script that runs a single postject inject via runInjectWorkerJob, isolating its ~138 MB buffer from the parent RSS.
ƒ Functions
job in the current process and writes the resulting report to job.reportPath. Call this to drive the worker logic without spawning a new Node process. The inject normally runs in a forked child because
postject.inject loads the entire ~121 MB host binary into a Node Buffer and rewrites it with the embedded blob: a single ~138 MB allocation that needs to be reclaimed on process exit rather than retained in the parent's RSS.Parameters
| Name | Type | Description |
|---|---|---|
§job | InjectWorkerJob | Descriptor describing the inject invocation. |
Returns
Promise< InjectWorkerReport>Example
Driving the worker logic in-process for a fixture
const report = await runInjectWorkerJob({
hostBinary: '/opt/node',
outputBinary: '/tmp/out',
blobPath: '/tmp/sea.blob',
resourceName: 'NODE_SEA_BLOB',
machoSegmentName: 'NODE_SEA',
sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2',
reportPath: '/tmp/report.json',
})◈ Interfaces
JSON.stringify / JSON.parse: no functions, no class instances.reportPath when the worker exits cleanly. Captured from the worker process: the parent never observes the ~138 MB postject buffer because the entire load lives and dies in the child.
Properties
JS bin synthesis primitives: rollup-driven bundling with shebang + bootstrap footer + chmod.
ƒ Functions
For each declared format (CJS / ESM), forks a rollup worker that bundles
src/bin/<name>.ts, prepends the #!/usr/bin/env node shebang, appends the resolved bootstrap footer (per-bin override or defaultBootstrap), writes the output to <outputPath>/bin/<name>.<ext>, and chmods it to 0o755 so the npm bin symlink is invokable. The worker isolates the heavy bin bundle (rollup + plugins + transitive deps loaded into the child) from the parent process. Output naming follows the convention:
- ESM:
<name>.mjs - CJS only:
<name>.js - CJS alongside ESM:
<name>.cjs.js
Parameters
| Name | Type | Description |
|---|---|---|
§bin | BinConfig | Bin declaration including name, format(s), optional runner override, and bootstrap override. |
§ctx | BuildContext | Resolved build context. |
Examples
Building a CJS-only bin
const outputs = await buildJsBin({ name: 'cz', format: 'cjs', runner: 'runCz' }, context)Building dual CJS + ESM outputs
const outputs = await buildJsBin({ name: 'hf-build', format: ['cjs', 'esm'] }, context)The footer wires the resolved runner export to
process.argv / process.cwd() / process.stderr / process.stdout, maps the returned exit code to process.exit, and surfaces unexpected rejections by writing to stderr and exiting 1. When
runner === 'default', the runner reference is resolved differently per format: - CJS uses
module.exports.default, available as a top-level reference inside the
- ESM uses
(await import(import.meta.url)).default, which re-imports the bundle's
await is required. Otherwise the supplied named runner is referenced directly, which works for both formats because Rollup preserves the local function name at the file scope.
Parameters
| Name | Type | Description |
|---|---|---|
§options | DefaultBootstrapOptions | Runner name + target format. |
Returns
stringoutput.footer.Examples
Default-export runner for an ESM bin
const footer = defaultBootstrap({ runner: 'default', format: 'esm' })Named runner shared between CJS and ESM outputs
const footer = defaultBootstrap({ runner: 'runCz', format: 'cjs' })◈ Interfaces
Properties
Bundle phase orchestrator: entry discovery, externals, Rollup, and declaration emission, run in order via runBundlePhase.
ƒ Functions
runBundlePhase( context: BuildContext, config: BuildConfig, monitor?: MemoryMonitor): Promise< FormatOutputs>
Iterates the format-specific configurations in
config, resolves the matching entry points for each format, and bundles every one. After all bundles are written, emits .d.ts declarations for the project exactly once.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
§config | BuildConfig | Top-level builder configuration. Only the format and tsConfig fields are consulted by this phase. |
§monitor? | MemoryMonitor | Optional memory monitor; when provided, peak heap inside the bundle phase is sampled at each format and declaration step. |
Returns
Promise< FormatOutputs>Example
Driving the bundle phase from a custom orchestrator
const formatOutputs = await runBundlePhase(context, config)◈ Interfaces
@swc-node/register loader when the worker is reached as TypeScript source rather than a compiled artifact).Properties
tsc-driven `.d.ts` emission, path flattening, the bundled-dep d.ts pre-pass, per-entry inlining, sibling-subpath dedup, and the emitted-type-surface check.
ƒ Functions
Namespace and star forms carry no names to falsify and are skipped; a default import is recorded as the
default name.Parameters
| Name | Type | Description |
|---|---|---|
§source | string | Raw text of the declaration file. |
Returns
EntryRef[ ]Example
Reading the cross-entry references of a subpath entry
collectEntryRefs("import { A } from '..';")
// => [{ specifier: '..', names: ['A'] }]index.d.ts exports. Returns
null for an open export set: a bare export * from '…' re-exports names this pass cannot enumerate, so no reference against that entry can be called dangling.Parameters
| Name | Type | Description |
|---|---|---|
§source | string | Raw text of the entry's index.d.ts. |
Returns
Set< string>null when the set is open.Example
Reading an entry's public type surface
collectExportedNames('export type { A } from "./a";\nexport declare const b: number;')
// => Set { 'A', 'b' }.d.ts (e.g., ../models, ../../models, ./bundle/declarations). Always omits the trailing
index.d.ts so both moduleResolution: 'bundler' and node accept it.Parameters
| Name | Type | Description |
|---|---|---|
§selfDtsPath | string | Absolute path to the current entry's index.d.ts. |
§sibling | SiblingEntry | The sibling entry being targeted. |
Returns
stringid in a rollup external resolution.Example
Computing a sibling import from `bundle/index.d.ts` to `models/`
computeSiblingSpecifier(
'/abs/dist/libs/foo/bundle/index.d.ts',
{ srcPath: 'models', indexDtsPath: '/abs/dist/libs/foo/models/index.d.ts' }
) // => '../models'/index and .d.ts suffixes. The plugin's
resolveId hook returns null for everything that does not resolve into a sibling: that lets the rest of the rollup-plugin-dts chain inline third-party type imports as before. Self-ownership is enforced by adding the current entry into the ownership pool: when the deepest-matching entry directory is the entry being rewritten (and not a sibling), the resolver returns
null so rollup keeps the path internal. This matters for the package root entry whose directory prefix- matches every other entry's path; without including self, root would spuriously claim its own subtree's files.Parameters
| Name | Type | Description |
|---|---|---|
§input | SiblingResolverInput | Self + sibling entry descriptors. |
Returns
PluginExample
Plugin instance for the bundle entry pass
const plugin = createSiblingExternalizePlugin({
selfSrcPath: 'bundle',
selfDtsPath: '/abs/dist/libs/foo/bundle/index.d.ts',
siblings: [{ srcPath: 'models', indexDtsPath: '/abs/dist/libs/foo/models/index.d.ts' }],
})srcPath to produce the absolute path of the entry's bundled index.d.ts.Parameters
Returns
string<outputPath>/<srcPath>/index.d.ts.Example
Computing the d.ts path for a sub-entry
dtsPathFor('/abs/dist/libs/foo', 'models') // => '/abs/dist/libs/foo/models/index.d.ts'srcPath.Parameters
Returns
T[ ]srcPath differs from the supplied one.Example
Building the sibling list for `bundle/`
const siblings = filterSiblings('bundle', allEntries)The per-entry d.ts pass rewrites any type import that resolves inside another entry's directory into a specifier targeting that entry (
'..', './feature'), on the assumption that the entry re-exports what its subtree declares. When it does not, the emitted reference resolves to nothing: skipLibCheck (on in every consumer scaffold) hides the broken declaration file, and the symbol silently degrades to an error type in consumer code instead: an event handler's parameter turning implicitly any, a re-exported payload type becoming unusable. This pass reads the emitted entry declarations back and reports each such reference.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
Returns
DanglingEntryRef[ ]Example
Auditing the emitted declarations after the per-entry pass
const dangling = findDanglingEntryRefs(context)absolutePath, or undefined when no sibling owns the path.Parameters
| Name | Type | Description |
|---|---|---|
§absolutePath | string | Resolved absolute path of an import. |
§siblings | SiblingEntry[ | Sibling-entry records. |
Returns
SiblingEntryundefined.Example
Identifying a sibling for a resolved type import
const owner = findOwningSibling('/abs/dist/libs/foo/models/index.d.ts', siblings)baseUrl=workspaceRoot) into the flat per-library structure consumers expect. Without this step tsc emits declarations at
dist/libs/<lib>/libs/<lib>/src/index.d.ts because it preserves the workspace-relative path. This primitive flattens that to dist/libs/<lib>/index.d.ts while preserving any nested platform / feature subdirectories. The copy is recursive and unconditional, so internal non-entry subdirectories that any entry's
index.d.ts re-exports from (e.g. shared/, lib/) are preserved rather than dropped. Per-source declarations left unreachable once the per-entry pass inlines each index.d.ts are removed afterwards by pruneOrphanDeclarations. Finally removes the leftover libs/, plugins/, and apps/ folders tsc created at the output root.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. Uses projectRoot, outputPath, and workspaceRoot. |
Example
Flattening declarations after a manual tsc invocation
flattenDeclarationPaths(context).d.ts and .d.ts.map files for every entry point in the project by spawning the workspace-local TypeScript compiler. After tsc finishes, calls
flattenDeclarationPaths to relocate the nested dist/<lib>/libs/<lib>/src/... structure that tsc emits with baseUrl=workspaceRoot back into the flat per-library shape consumers expect.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. Provides project root, output path, tsconfig path, workspace root, and entry point discovery for the flatten step. |
Returns
Promise< GenerateDeclarationsResult>Example
Generating declarations as part of a custom build
const result = await generateDeclarations(context)
console.log(result.stdout).d.ts / .d.ts.map files left behind once the per-entry flatten has inlined each entry's declarations into a self-contained index.d.ts. Walks the whole package tree (excluding
_dependencies/) and deletes every declaration file not reachable from an entry-point index.d.ts via its transitive relative specifiers. Reachability (rather than a per-directory sweep) guarantees the invariant that after this runs no surviving .d.ts references a removed one: when the flatten made index.d.ts self-contained the reachable set collapses to the roots and every per-source sibling is pruned; when the flatten was skipped the siblings the public entry still re-exports (and anything they transitively re-export) stay live so the shipped types resolve. This removes the redundancy at the source so dist == the shipped tarball, demoting package.json#files to a backstop. Safety rails:
- Never touches anything inside
_dependencies/. - A
.d.ts.mapis kept iff its sibling.d.tsis reachable. - A dynamic (non-literal)
import(/require(in any reached declaration
Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
Returns
number.d.ts / .d.ts.map files removed.Example
Pruning orphans after generateDeclarations
const removed = pruneOrphanDeclarations(context)rollup-plugin-dts over every tsc-emitted entry .d.ts so (a) local per-source sibling re-exports (export … from './create-logger') are flattened into the entry's index.d.ts, and (b) bundled-dep type imports are routed through _dependencies/<dep>/index.d.ts rather than left as bare specifiers. This self-containment is what lets pruneOrphanDeclarations delete the orphaned per-source .d.ts afterwards without orphaning a still-referenced sibling. Runs whenever the build bundles any dep: npm (
bundledDeps) or workspace (workspaceBundledDeps); a package whose deps are all @hyperfrontend/* still needs the flatten. Entries whose tsc output is missing are skipped silently: the bundle phase may have skipped them deliberately (e.g., empty bundles).Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
§monitor? | MemoryMonitor | Optional memory monitor invoked between jobs. |
Example
Inlining bundled-dep types into every entry's .d.ts after tsc emission
await runDtsPerEntry(context)_dependencies/<dep>/index.d.ts by running rollup-plugin-dts over the dep's types entry. Cross-dep type imports are marked external so the per-entry d.ts pass can route them through _dependencies/.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
§monitor? | MemoryMonitor | Optional memory monitor invoked between jobs. |
Example
Producing _dependencies/<dep>/index.d.ts for a self-contained build
await runDtsPrePass(context)any in consumer code.Parameters
| Name | Type | Description |
|---|---|---|
§context | BuildContext | Resolved build context. |
Example
Verifying the type surface after the declaration passes
verifyEntryTypeRefs(context)◈ Interfaces
index.d.ts to a sibling entry that names symbols the sibling does not export.Properties
Properties
Properties
Each sibling represents another entry-point in the same library that has its own bundled
index.d.ts. The per-entry d.ts pass externalizes imports that resolve into a sibling's directory so consumers see one canonical type surface (e.g., import type { EntryPoint } from '../models' instead of an inlined declaration in every entry).Properties
Properties
Additive post-emit pass that hoists first-party modules inlined into multiple per-entry bundles into shared chunks. See hoistSharedFirstParty.
ƒ Functions
Declarations whose base name is unowned (a dependency or unexported helper inlined into the bundle) are skipped, leaving them in place.
Parameters
| Name | Type | Description |
|---|---|---|
§parsed | ParsedEntry | A parsed entry bundle. |
§owners | OwnerIndex | The first-party ownership index. |
Example
Attributing an entry's declarations
const byModule = attribute(parseEntry(source, 'esm'), owners)$N collision-rename suffix from a local name, yielding the canonical source symbol name.Parameters
| Name | Type | Description |
|---|---|---|
§name | string | A local identifier from an emitted bundle. |
Returns
string$digits removed.Example
Undoing a collision rename
baseName('Store$1') // => 'Store'Parameters
| Name | Type | Description |
|---|---|---|
§format | ChunkFormat | Output module format. |
Returns
stringindex.esm.js / index.cjs.js).Example
Naming the CJS chunk
chunkFileName('cjs') // => 'index.cjs.js'.ts module file reachable through relative specifiers. Both static
import / export ... from declarations and string-literal dynamic import() calls are followed. Bare specifiers (dependencies and workspace packages) and non-.ts targets (e.g. imported .json data) are outside the graph, as is any resolution escaping srcRoot. Files a library carries but never imports from an entry point — spec-only fixtures above all — are unreachable by construction, which is what keeps their declarations out of the dedupe ownership index.Parameters
Returns
string[ ]Example
Reachable modules of a single-entry library
const files = collectReachableSources(['/abs/libs/foo/src/index.ts'], '/abs/libs/foo/src')Two copies of the same source that differ only in rollup's per-entry
$N collision suffixes compare equal, while genuinely different code never collides: $N is stripped from identifier nodes only, so string literals, comments, numeric tokens, and discriminating member names stay intact.Parameters
Returns
string$N stripped from every identifier node.Example
Stripping a dep-namespace local's collision suffix
fingerprintOf(statement, sourceFile) // 'const x = index_cjs_js.getType();' for both `$1` and `$2` copiesWhen
entryFiles is given, only modules reachable from those entry sources through the first-party import graph are indexed; a file the entries never import — a spec-only fixture above all — can then never own a name, so a bundle declaration merely named like one of its symbols (a tree-shaken JSON key, say) stays unattributed instead of forming a phantom chunk. Without entryFiles the whole <srcRoot>/** tree is scanned. Both exported and private top-level runtime declarations are indexed, so a module's private helpers hoist alongside the exports that use them. Types-only modules declare no runtime symbols and contribute nothing. A name declared by two different modules is ambiguous and dropped from the index, so the pass can never misattribute it.
Parameters
Returns
OwnerIndexExample
Indexing only what a library's entries reach
const owners = indexOwners('/abs/libs/foo/src', ['/abs/libs/foo/src/index.ts'])Classifies every top-level statement as an import binding, a removable runtime declaration, the export surface, or an opaque bare statement. Inline
export-modified declarations are treated as part of the export surface (never removable), so the bundle's published API is never disturbed.Parameters
| Name | Type | Description |
|---|---|---|
§source | string | Raw entry bundle source text. |
§format | ChunkFormat | Module format selecting ESM vs CJS import/export shapes. |
Returns
ParsedEntryExample
Modeling an ESM entry bundle
const parsed = parseEntry("import { x } from './_dependencies/a/index.esm.js'\nclass C {}", 'esm')A module qualifies only when it is inlined into at least two entries, is not entangled with a bare statement, presents structurally identical declarations (rename-insensitive) in a consistent order across every copy, references nothing unresolvable, and (after closure and acyclic peeling) depends only on other hoisted modules through a cycle-free graph. Anything failing these is left inlined, so the worst case equals the unmodified output.
Parameters
| Name | Type | Description |
|---|---|---|
§entries | EntryInput[ | Parsed entries with their per-module attribution. |
§owners | OwnerIndex | First-party ownership index. |
Returns
Map< string, PlannedModule>Example
Planning hoists for a set of entries
const plan = planHoists(entries, owners)Parameters
| Name | Type | Description |
|---|---|---|
§plan | ChunkPlan | The module's declarations plus resolved import edges. |
§format | ChunkFormat | Output module format. |
Returns
stringExample
Rendering an ESM chunk
const source = renderChunk({ decls, crossImports: [], depImports: [] }, 'esm')resolveModuleRefs( decls: EntryDecl[ ], owners: OwnerIndex, importBindings: Map< string, ImportBinding>, entryDeclNames: Set< string>, selfModuleKey: string): ModuleResolution
Reference collection is scope-aware: a name bound inside a declaration (a function parameter, a hoisted
var, a block-scoped local) is never a reference, so a callback parameter that happens to share an owned symbol's name can never fabricate a cross-module import edge. Identifiers the module declares itself are intra-chunk and ignored. A name that is neither owned, nor a dependency binding, nor a top-level entry declaration is a runtime global and needs no import. Cross-module references are always safe to lift because planHoists only keeps an acyclic subset, so every dependency chunk is fully evaluated before its dependent.Parameters
| Name | Type | Description |
|---|---|---|
§decls | EntryDecl[ | The module's canonical declarations. |
§owners | OwnerIndex | First-party ownership index. |
§importBindings | Map< | The consuming entry's import bindings. |
§entryDeclNames | Set< | Every top-level declaration name in the entry. |
§selfModuleKey | string | The module being resolved. |
Returns
ModuleResolutionExample
Resolving a module's references
const resolution = resolveModuleRefs(decls, owners, parsed.importBindings, parsed.declNames, 'events/events')_shared/ chunks instead of inlining them. Splices every hoisted declaration (and its leading comments) out of the entry and prepends an import/
require binding the chunk's base export to the entry's local name, aliasing when rollup renamed the local (foo as foo$1). Only the hoisted symbols the entry still references after splicing are re-imported: a private helper used solely by another hoisted export (e.g. a reducer's handlers, used only by the hoisted rootReducer) is seen as dead against the spliced body and omitted from the import, while the chunk it lives in keeps it. The bundle's export surface is left untouched: surviving spliced names are now supplied by the inserted imports, so the published API is byte-for-byte identical to before.Parameters
| Name | Type | Description |
|---|---|---|
§parsed | ParsedEntry | The parsed entry bundle. |
§hoists | EntryHoist[ | The modules hoisted out of this entry, each with its chunk specifier. |
§format | ChunkFormat | Output module format. |
Returns
stringhoists is empty.Example
Rewriting an entry to import a shared module
const rewritten = rewriteEntry(parseEntry(source, 'esm'), [{ decls, specifier: './_shared/state/index.esm.js' }], 'esm')◈ Interfaces
Properties
Properties
Properties
Properties
kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"Binding shape governing how the import is re-emitted.Properties
Properties
fingerprint:stringRename-insensitive identity key: $N suffixes stripped from identifier nodes; the raw text is still the chunk-body source.Properties
Properties
Properties
Properties
kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"Shape of the binding, governing how it is re-emitted into a shared chunk.Properties
Properties
◆ Types
src/, without extension and forward-slashed (e.g. events/events, models). Doubles as the directory name the module's hoisted chunk lives under: _shared/<moduleKey>/.type ModuleKey = stringPer-format pre-pass and externalize plugin that bundle each third-party (and workspace) dep once, then reroute every entry's import to it.
ƒ Functions
WorkspaceBundledDep entries into per-package WorkspaceBundledDepRoute shapes consumable by createExternalizeBundledDepsPlugin. Whole-surface deps emit a single route with no specifiers; sub-path deps emit a route enumerating each pre-passed specifier so the plugin can match exactly.
Parameters
| Name | Type | Description |
|---|---|---|
§entries | WorkspaceBundledDep[ | Workspace bundled-dep entries from BuildContext.workspaceBundledDeps. |
Returns
WorkspaceBundledDepRoute[ ]Example
Building plugin routes from the build context
const routes = buildWorkspaceRoutes(context.workspaceBundledDeps)
createExternalizeBundledDepsPlugin({ deps, entryOutDir, format, depsRoot, workspaceRoutes: routes })resolveId hook maps any import of a bundled dep (or its subpath) to a relative import that points at the pre-passed artifact under _dependencies/<dep>/. With workspaceRoutes populated, the plugin also routes workspace @hyperfrontend/ imports through the matching workspace chunk (whole-surface or sub-path mode per route). The plugin marks node builtins (and
node: imports) as external so they survive untouched, and returns null for everything else so the rest of the plugin chain can resolve normally.Parameters
| Name | Type | Description |
|---|---|---|
§options | ExternalizeBundledDepsPluginOptions | Plugin configuration. |
Returns
PluginExample
Routing imports of `rollup` to the pre-passed copy
const plugin = createExternalizeBundledDepsPlugin({
deps: ['rollup'],
entryOutDir: '/abs/dist/libs/foo/bundle/rollup',
format: 'esm',
depsRoot: '/abs/dist/libs/foo/_dependencies',
})tsconfig.base.json at the workspace root and falling back to a tsconfig.json extends chain when the base form is absent.Parameters
| Name | Type | Description |
|---|---|---|
§workspaceRoot | string | Absolute workspace root. |
Returns
Map< string, string[ ]>paths key (e.g. @hyperfrontend/logging), with values pointing at absolute source files.Example
Loading the workspace's path-mapping table
const paths = loadWorkspacePathMappings('/abs/repo')
paths.get('@hyperfrontend/logging') // => ['/abs/repo/libs/logging/src/index.ts']fromDir needs to use to reach toFile. The result always uses POSIX separators and starts with ./ or ../ so node treats it as a relative path.Parameters
Returns
stringExample
Computing the relative path to a bundled dep
relativeImport('/abs/dist/libs/foo/bundle/rollup', '/abs/dist/libs/foo/_dependencies/rollup/index.esm.js')
// => '../../_dependencies/rollup/index.esm.js'_dependencies/<dep>/) for the build. Algorithm:
- Read
dependenciesfrom the project'spackage.json. - Subtract
peerDependencies: those stay external. - Subtract anything matching
isWorkspacePackage: workspace deps are inlined per the existing flow. - Apply
include/excludeoverrides.
Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonPath | string | Absolute path to the project's package.json. |
§options | ResolveBundledDepsOptions | Caller overrides.(default: {}) |
Returns
string[ ]Example
Resolving bundled deps for a workspace library
const deps = resolveBundledDeps('/abs/libs/foo/package.json', {
isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/'),
})bundle/dependencies/worker. This works whether the builder runs from its built dist, an installed node_modules copy, or melded into a host bundle under _dependencies/. The compiled index.cjs.js is preferred; an index.ts sibling resolves with the @swc-node/register loader for source-mode bootstrap.Parameters
| Name | Type | Description |
|---|---|---|
§startDir? | string | Directory to begin the ascent from. Defaults to the running module's directory; pass an explicit value to resolve from another anchor or under test. |
Returns
WorkerInvocationundefined if no worker is found under any ancestor.Example
Locating the worker beside the builder
const invocation = resolveDefaultWorkerPath()
if (!invocation) throw new Error('builder worker artifact not found')resolveWorkspaceBundledDeps( packageJsonPath: string, workspaceRoot: string, options: ResolveWorkspaceBundledDepsOptions): ResolvedWorkspaceDepEntry[ ]
@hyperfrontend/* deps that should be hoisted into _dependencies/<name>(/<sub>)?/index.<ext>.js. Algorithm:
- Read the project's
package.json#dependencies, retain entries matching
isWorkspacePackage, and apply caller include / exclude overrides. Peer deps and excluded packages are skipped; include cannot resurrect a peer dep. - Load workspace path-mappings (tsconfig
paths). - For each eligible workspace dep, apply the per-dep hoist policy
options.policy, defaulting to 'sub-path'): 'sub-path'(the zero-config default) emits one entry per resolvable
'whole-surface'is an explicit opt-in collapse: it emits a single entry
- The returned list is sorted by
specifierfor stable downstream ordering.
bundleAllDeps, an eligible dep that cannot be fully resolved is a contract violation, not a soft skip. The function throws (rather than silently externalising) when an eligible dep has no resolvable tsconfig mapping, when a mapped source has no owning tsconfig, or when a dep is explicitly opted into 'whole-surface' yet exposes no root export, each with a message naming the dep and the remedy.Parameters
| Name | Type | Description |
|---|---|---|
§packageJsonPath | string | Absolute path to the project's package.json. |
§workspaceRoot | string | Absolute workspace root used to load tsconfig.base.json paths. |
§options | ResolveWorkspaceBundledDepsOptions | Caller overrides + workspace-package predicate + per-package policy map. |
Returns
ResolvedWorkspaceDepEntry[ ]Example
Resolving workspace pre-pass entries for builder
const entries = resolveWorkspaceBundledDeps(
'/abs/libs/builder/package.json',
'/abs/repo',
{ isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/') }
)Each child writes a JSON report to a parent-supplied path; this function reads the report after the child exits and accumulates per-job statistics. If any worker exits non-zero or fails to produce a report, the function throws with the failed job's context.
The report directory is created in the OS temp dir and removed before returning, regardless of success or failure.
Parameters
| Name | Type | Description |
|---|---|---|
§jobs | PrePassJob[ | Pre-pass jobs to run. |
§options | RunPrePassOptions | Worker path + optional memory monitor. |
Returns
Promise< PrePassResult[ ]>Example
Pre-passing rollup and one of its plugins
const results = await runPrePass(jobs, { workerPath: '/abs/dist/libs/builder/bundle/dependencies/worker.cjs.js' })job and writes the resulting report to job.reportPath. Public so callers (and tests) can drive the worker logic without spawning a new Node process.
Parameters
| Name | Type | Description |
|---|---|---|
§job | PrePassWorkerJob | Job spec describing the rollup invocation. |
Returns
Promise< PrePassWorkerReport>Example
Driving the worker logic in-process for a fixture
const report = await runPrePassWorkerJob({ kind: 'js', dep: 'rollup', ... })◈ Interfaces
Properties
deps:string[ ]Bundled-dep package names. Any import of these (or their subpaths) is rerouted to _dependencies/<dep>/.workspaceRoutes?:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes. Imports of these packages reroute to _dependencies/<packageName>(/<sub>)?/.Properties
depsRoot?:stringAbsolute path to the project's _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.kind:PrePassJobKindPre-pass kind. js and dts cover npm bundled deps; workspace-js and workspace-dts cover workspace @hyperfrontend/* deps whose entries are TypeScript source.npmDeps?:string[ ]NPM bundled-dep names consumed by the worker's externalize plugin to rewrite cross-dep imports to relative paths under depsRoot. Disjoint from workspaceRoutes.otherDeps:string[ ]Other deps in the pre-pass set; prefix-matched and marked external so cross-dep imports stay link-time.otherWorkspaceSpecifiers?:string[ ]Sub-path-mode workspace specifiers in the pre-pass set; matched as exact specifier only. Used by workspace-* jobs so sibling sub-paths externalize cleanly (e.g., one built-in-copy/<x> chunk does not pull in another).selfDtsPath?:stringAbsolute path to the input file's owning entry directory (used to compute sibling specifiers).siblingEntries?:SiblingEntryDescriptor[ ]Sibling-entry descriptors used by the per-entry dts pass to externalize imports that resolve into another entry's directory. See SiblingEntryDescriptor. Empty / omitted for dep pre-pass jobs.workspaceRoot?:stringAbsolute workspace root used as baseUrl for path-mapping resolution (workspace-* jobs only).workspaceRoutes?:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes consumed by the worker's externalize plugin. For self-pre-pass jobs (workspace-js / workspace-dts) this excludes the specifier or package being built so the chunk inlines its own internals.Properties
process.argv[2]. Each invocation produces exactly one rollup output and one JSON report at
reportPath so the parent orchestrator can collect per-job statistics.Properties
depsRoot?:stringAbsolute path to the project's _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.format:"cjs" | "esm"Output format. JS jobs must use 'esm' or 'cjs'. dts jobs always use 'es' internally.inputPath:stringAbsolute path to the dep's entry (main / module for JS, types for dts, source .ts for workspace-*).kind:PrePassWorkerJobKindPre-pass kind. js and dts run the npm-dep pipeline; workspace-js and workspace-dts add @rollup/plugin-typescript (or rollup-plugin-dts's tsconfig integration) so TypeScript source workspace deps can be hoisted.npmDeps?:string[ ]NPM bundled-dep names consumed by the canonical externalize plugin to rewrite cross-dep imports to relative paths under depsRoot.otherDeps:string[ ]Other deps in the pre-pass set; marked external so cross-dep imports stay link-time. Prefix-matched.otherWorkspaceSpecifiers?:string[ ]Sub-path-mode workspace specifiers in the pre-pass set; matched as exact specifier only (e.g. @hyperfrontend/immutable-api-utils/built-in-copy/array). Used by workspace-* jobs so sibling sub-paths externalize cleanly without also externalizing every other sub-path on the same package.selfDtsPath?:stringAbsolute path to the input file's owning entry directory (used to compute sibling specifiers).selfSrcPath?:stringOwning entry's srcPath (used for diagnostics). Empty string for the package root.siblingEntries?:SiblingEntry[ ]Sibling-entry descriptors used by the per-entry dts pass to externalize imports that resolve into another entry's directory. Empty / omitted for dep pre-pass jobs.workspaceRoot?:stringAbsolute workspace root used as baseUrl for path-mapping resolution (workspace-* jobs only).workspaceRoutes?:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes consumed by the canonical externalize plugin. For self-pre-pass jobs (workspace-js / workspace-dts) this excludes the specifier or package being built so the chunk inlines its own internals.reportPath when a worker exits cleanly.Properties
Properties
isWorkspacePackage?:IsWorkspacePackagePredicatePredicate identifying workspace-internal packages; when matched, packages are NOT pre-passed.Properties
policy:WorkspaceDepHoistPolicyHoist policy applied to this entry's package; carried through so callers need not re-derive it.specifier:stringPublic import specifier this entry resolves: <packageName> or <packageName>/<subPath>.tsConfigPath:stringAbsolute path to the dep's own tsconfig used by @rollup/plugin-typescript during pre-pass.Properties
policy?:Record< string, WorkspaceDepHoistPolicy>Per-package hoist-policy override, keyed by package name. Packages absent from the map default to 'sub-path' (granular, zero-config). Set a package to 'whole-surface' to opt into collapsing its sub-paths onto the root chunk. No built-in entries; callers supply their own opinions.Properties
execArgv?:string[ ]Extra arguments prepended to the worker invocation (e.g. ['--require', '@swc-node/register']).policy: 'whole-surface'collapses every import of<packageName>(root or
_dependencies/<packageName>/index.<ext>. policy: 'sub-path'matches each pre-passed specifier exactly; non-matched
Properties
◆ Types
type ChunkFormat = "esm" | "cjs"D.ts passes use
'dts' so the plugin maps imports to .d.ts siblings under _dependencies/<dep>/; JS passes use 'esm' / 'cjs'.type ExternalizeFormat = "esm" | "cjs" | "dts"PrePassWorkerJobKind on the worker side.type PrePassJobKind = "js" | "dts" | "workspace-js" | "workspace-dts"Forked-worker entry script that runs a single dependency pre-pass via runPrePassWorkerJob.
ƒ Functions
job and writes the resulting report to job.reportPath. Public so callers (and tests) can drive the worker logic without spawning a new Node process.
Parameters
| Name | Type | Description |
|---|---|---|
§job | PrePassWorkerJob | Job spec describing the rollup invocation. |
Returns
Promise< PrePassWorkerReport>Example
Driving the worker logic in-process for a fixture
const report = await runPrePassWorkerJob({ kind: 'js', dep: 'rollup', ... })◈ Interfaces
process.argv[2]. Each invocation produces exactly one rollup output and one JSON report at
reportPath so the parent orchestrator can collect per-job statistics.Properties
depsRoot?:stringAbsolute path to the project's _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.format:"cjs" | "esm"Output format. JS jobs must use 'esm' or 'cjs'. dts jobs always use 'es' internally.inputPath:stringAbsolute path to the dep's entry (main / module for JS, types for dts, source .ts for workspace-*).kind:PrePassWorkerJobKindPre-pass kind. js and dts run the npm-dep pipeline; workspace-js and workspace-dts add @rollup/plugin-typescript (or rollup-plugin-dts's tsconfig integration) so TypeScript source workspace deps can be hoisted.npmDeps?:string[ ]NPM bundled-dep names consumed by the canonical externalize plugin to rewrite cross-dep imports to relative paths under depsRoot.otherDeps:string[ ]Other deps in the pre-pass set; marked external so cross-dep imports stay link-time. Prefix-matched.otherWorkspaceSpecifiers?:string[ ]Sub-path-mode workspace specifiers in the pre-pass set; matched as exact specifier only (e.g. @hyperfrontend/immutable-api-utils/built-in-copy/array). Used by workspace-* jobs so sibling sub-paths externalize cleanly without also externalizing every other sub-path on the same package.selfDtsPath?:stringAbsolute path to the input file's owning entry directory (used to compute sibling specifiers).selfSrcPath?:stringOwning entry's srcPath (used for diagnostics). Empty string for the package root.siblingEntries?:SiblingEntry[ ]Sibling-entry descriptors used by the per-entry dts pass to externalize imports that resolve into another entry's directory. Empty / omitted for dep pre-pass jobs.workspaceRoot?:stringAbsolute workspace root used as baseUrl for path-mapping resolution (workspace-* jobs only).workspaceRoutes?:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes consumed by the canonical externalize plugin. For self-pre-pass jobs (workspace-js / workspace-dts) this excludes the specifier or package being built so the chunk inlines its own internals.reportPath when a worker exits cleanly.Properties
Entry-point discovery, resolution, and platform filtering primitives.
ƒ Functions
Scans
<projectRoot>/src for the root index.ts plus every nested directory containing an index.ts. Recognized layouts include: src/index.ts: root entrysrc/browser/index.ts,src/node/index.ts: platform entriessrc/<feature>/index.ts: feature entriessrc/<platform>/<feature>/index.ts: nested entries (max depth 3)
Parameters
| Name | Type | Description |
|---|---|---|
§projectRoot | string | Absolute path to the project root. |
Returns
EntryPointDiscoveryExample
Discovering entries for a barrel library
const discovery = discoverEntries('/abs/path/to/libs/utils')
discovery.category // => 'feature' | 'platform' | 'hybrid' | ...
discovery.entryPoints // => [{ exportPath: '.', ... }, ...]getEntriesByPlatform( discovery: EntryPointDiscovery, platform: EntryPointPlatform): EntryPoint[ ]
Parameters
| Name | Type | Description |
|---|---|---|
§discovery | EntryPointDiscovery | Discovery result returned by discoverEntries. |
§platform | EntryPointPlatform | Platform hint to filter on. |
Returns
EntryPoint[ ]discovery.entryPoints flagged with the matching platform.Example
Retrieving browser entries
const browserEntries = getEntriesByPlatform(discovery, 'browser')FormatEntryConfig's entry and exclude patterns. Patterns may be exact subpaths (
./browser), globs (./browser/*), or arrays of either. When entry is omitted every discovered entry is considered, then exclude removes matches.Parameters
| Name | Type | Description |
|---|---|---|
§config | FormatEntryConfig | Format-level entry configuration. |
§discoveredEntries | EntryPoint[ | Entry points returned by discoverEntries. |
Returns
EntryPoint[ ]Example
Selecting only browser entries
resolveEntries({ entry: './browser/*' }, discovery.entryPoints)Externals resolution primitives: package.json scanning and globals validation for IIFE / UMD bundles.
ƒ Functions
The output combines:
- top-level
dependenciesfrom the project'spackage.json peerDependencies(always external regardless ofbundleWorkspaceDeps)- the caller-supplied
additionallist
bundleWorkspaceDeps is true and a workspace predicate is supplied, packages matching the predicate are stripped from dependencies and additional so they get inlined by the bundler. Peer dependencies are always preserved.Parameters
| Name | Type | Description |
|---|---|---|
§options | ResolveExternalsOptions | Inputs controlling the resolution. |
Returns
string[ ]Example
Resolving externals while inlining workspace deps
const external = resolveExternals({
packageJsonPath: '/abs/libs/foo/package.json',
isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/'),
bundleWorkspaceDeps: true,
})globals map. Throws an aggregated error listing the missing entries when the configuration is incomplete; resolves silently otherwise.
Parameters
Example
Failing fast when a globals mapping is missing
validateExternalsConfig(['react'], { 'react-dom': 'ReactDOM' })
// => throws: Missing globals mapping for external dependencies: react◈ Interfaces
resolveExternals.Properties
additional?:string[ ]Additional package names to mark external regardless of the package.json contents.bundledDeps?:string[ ]Third-party package names being bundled into _dependencies/. When non-empty, these are removed from the resolved external list so their imports are routed to the bundled copies instead of left external.bundleWorkspaceDeps?:booleanWhen true, workspace packages are inlined and stripped from the resolved external list.isWorkspacePackage?:IsWorkspacePackagePredicatePredicate identifying workspace-internal packages. Defaults to "everything is external".workspaceBundledDepNames?:string[ ]Workspace package names being bundled into _dependencies/. When non-empty, these are removed from the resolved external list so their imports are routed to the bundled copies instead of left external.Rollup driver: per-format descriptor builders and the per-entry forked-worker dispatcher.
ƒ Functions
dispatchRollupWorker( descriptor: RollupBuildDescriptor, options: DispatchRollupWorkerOptions): Promise< RollupWorkerReport>
reportPath is overwritten with a temp-dir path created by this function. Each worker writes a JSON report at the temp path; this function reads it after the child exits and returns the per-job statistics. If the worker exits non-zero or fails to produce a report, the function throws with a label-rich message.
The temp report directory is cleaned up before returning, regardless of success or failure.
Parameters
| Name | Type | Description |
|---|---|---|
§descriptor | RollupBuildDescriptor | Build descriptor to dispatch. |
§options | DispatchRollupWorkerOptions | Worker path + optional memory monitor. |
Returns
Promise< RollupWorkerReport>Example
Dispatching a single ESM build
const report = await dispatchRollupWorker(descriptor, { workerPath: '/abs/dist/.../worker.cjs.js' })bundle/rollup/worker. This works whether the builder runs from its built dist, an installed node_modules copy, or melded into a host bundle under _dependencies/. The compiled index.cjs.js is preferred; an index.ts sibling resolves with the @swc-node/register loader for source-mode bootstrap.Parameters
| Name | Type | Description |
|---|---|---|
§startDir? | string | Directory to begin the ascent from. Defaults to the running module's directory; pass an explicit value to resolve from another anchor or under test. |
Returns
RollupWorkerInvocationundefined if no worker is found under any ancestor.Example
Locating the worker beside the builder
const invocation = resolveDefaultRollupWorkerPath()
if (!invocation) throw new Error('builder rollup worker artifact not found')job and writes the resulting report to job.reportPath. Use this to drive the worker logic in-process; use dispatchRollupWorker to run the same job in a forked Node process.
Parameters
| Name | Type | Description |
|---|---|---|
§job | RollupBuildDescriptor | Descriptor describing the rollup invocation. |
Returns
Promise< RollupWorkerReport>Example
Driving the worker logic in-process for a fixture
const report = await runRollupWorkerJob({ format: 'esm', inputFile: '/abs/in.ts', ... })toBinBuildDescriptor( bin: BinConfig, context: BuildContext, format: BinScriptFormat, formats: BinScriptFormat[ ], reportPath: string): RollupBuildDescriptor
Bins are self-contained executable scripts: workspace deps are inlined and the worker writes to
<outputPath>/bin/<name>.<ext> with the shebang banner, the resolved bootstrap footer, and chmod 0o755 applied. Output naming convention:
- ESM:
<name>.mjs - CJS only:
<name>.js - CJS alongside ESM:
<name>.cjs.js
Parameters
| Name | Type | Description |
|---|---|---|
§bin | BinConfig | Bin declaration including name, runner, and per-bin bootstrap override. |
§context | BuildContext | Resolved build context. |
§format | BinScriptFormat | Format being built (one of bin.format's entries). |
§formats | BinScriptFormat[ | Full list of formats requested for this bin (drives the CJS filename). |
§reportPath | string | Absolute path the worker will write its JSON report to. |
Returns
RollupBuildDescriptorExample
Producing the descriptor for an `hf-build` CJS bin alongside an ESM twin
const descriptor = toBinBuildDescriptor(bin, context, 'cjs', ['cjs', 'esm'], '/tmp/r.json')toCjsBuildDescriptor( entry: EntryPoint, config: CjsConfig, context: BuildContext, reportPath: string): RollupBuildDescriptor
Parameters
| Name | Type | Description |
|---|---|---|
§entry | EntryPoint | Entry point to compile. |
§config | CjsConfig | CJS-format configuration. |
§context | BuildContext | Resolved build context. |
§reportPath | string | Absolute path the worker will write its JSON report to. |
Returns
RollupBuildDescriptorExample
Producing the descriptor for the root entry
const descriptor = toCjsBuildDescriptor(entry, cjsConfig, context, '/tmp/report.json')toEsmBuildDescriptor( entry: EntryPoint, config: EsmConfig, context: BuildContext, reportPath: string): RollupBuildDescriptor
The returned descriptor is fully serializable (no functions), ready to be passed to dispatchRollupWorker.
Parameters
| Name | Type | Description |
|---|---|---|
§entry | EntryPoint | Entry point to compile. |
§config | EsmConfig | ESM-format configuration. |
§context | BuildContext | Resolved build context. |
§reportPath | string | Absolute path the worker will write its JSON report to. |
Returns
RollupBuildDescriptorExample
Producing the descriptor for the root entry
const descriptor = toEsmBuildDescriptor(entry, esmConfig, context, '/tmp/report.json')toIifeBuildDescriptor( entry: EntryPoint, config: IifeConfig, context: BuildContext, reportPath: string): RollupBuildDescriptor
Validates the externals/globals pairing eagerly, throwing before the descriptor is returned.
Parameters
| Name | Type | Description |
|---|---|---|
§entry | EntryPoint | Entry point to bundle. |
§config | IifeConfig | IIFE-format configuration. |
§context | BuildContext | Resolved build context. |
§reportPath | string | Absolute path the worker will write its JSON report to. |
Returns
RollupBuildDescriptorExample
Producing the descriptor for an IIFE bundle
const descriptor = toIifeBuildDescriptor(entry, iifeConfig, context, '/tmp/report.json')toUmdBuildDescriptor( entry: EntryPoint, config: UmdConfig, context: BuildContext, reportPath: string): RollupBuildDescriptor
Validates the externals/globals pairing eagerly, throwing before the descriptor is returned.
Parameters
| Name | Type | Description |
|---|---|---|
§entry | EntryPoint | Entry point to bundle. |
§config | UmdConfig | UMD-format configuration. |
§context | BuildContext | Resolved build context. |
§reportPath | string | Absolute path the worker will write its JSON report to. |
Returns
RollupBuildDescriptorExample
Producing the descriptor for a UMD bundle
const descriptor = toUmdBuildDescriptor(entry, umdConfig, context, '/tmp/report.json')◈ Interfaces
JSON.stringify / JSON.parse: no functions, no class instances. The worker reconstructs
RollupOptions from the descriptor using the same plugin factories the parent would have used in-process.Properties
bin:RollupWorkerBinBin-output config carried by ESM / CJS descriptors that emit an executable bin. null for non-bin entries.bundle:RollupWorkerBundleOutputBundle-output config carried by IIFE / UMD descriptors. null for esm/cjs.bundledDepsPlugin:RollupWorkerBundledDepsPluginWhen set: install the externalize-bundled-deps plugin (esm/cjs only).workspaceRoutes:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes consumed by the externalize plugin. When non-empty, imports of these workspace packages (and matching sub-paths) are rerouted to the corresponding _dependencies/<packageName>(/<sub>)?/index.<ext> chunk. Empty array for descriptors that do not opt into workspace-dep hoisting.package.json#bin executable script. When set, the worker writes the bundle to outputFile (instead of the format's default
index.<fmt>.js filename), prepends the banner and appends the footer, and applies chmod to the produced file so the npm bin symlink is invokable.Captures the format-specific output knobs the worker needs to reconstruct
OutputOptions for a self-contained browser bundle.Properties
--require \@swc-node/register when the worker is loaded from TypeScript source during a bootstrap build).Properties
reportPath when the worker exits cleanly.Properties
◆ Types
type RollupWorkerFormat = "esm" | "cjs" | "iife" | "umd"Forked-worker entry script that runs a single per-entry rollup pass via runRollupWorkerJob.
ƒ Functions
job and writes the resulting report to job.reportPath. Use this to drive the worker logic in-process; use dispatchRollupWorker to run the same job in a forked Node process.
Parameters
| Name | Type | Description |
|---|---|---|
§job | RollupBuildDescriptor | Descriptor describing the rollup invocation. |
Returns
Promise< RollupWorkerReport>Example
Driving the worker logic in-process for a fixture
const report = await runRollupWorkerJob({ format: 'esm', inputFile: '/abs/in.ts', ... })◈ Interfaces
JSON.stringify / JSON.parse: no functions, no class instances. The worker reconstructs
RollupOptions from the descriptor using the same plugin factories the parent would have used in-process.Properties
bin:RollupWorkerBinBin-output config carried by ESM / CJS descriptors that emit an executable bin. null for non-bin entries.bundle:RollupWorkerBundleOutputBundle-output config carried by IIFE / UMD descriptors. null for esm/cjs.bundledDepsPlugin:RollupWorkerBundledDepsPluginWhen set: install the externalize-bundled-deps plugin (esm/cjs only).workspaceRoutes:WorkspaceBundledDepRoute[ ]Workspace bundled-dep routes consumed by the externalize plugin. When non-empty, imports of these workspace packages (and matching sub-paths) are rerouted to the corresponding _dependencies/<packageName>(/<sub>)?/index.<ext> chunk. Empty array for descriptors that do not opt into workspace-dep hoisting.package.json#bin executable script. When set, the worker writes the bundle to outputFile (instead of the format's default
index.<fmt>.js filename), prepends the banner and appends the footer, and applies chmod to the produced file so the npm bin symlink is invokable.Captures the format-specific output knobs the worker needs to reconstruct
OutputOptions for a self-contained browser bundle.Properties
reportPath when the worker exits cleanly.Properties
◆ Types
type RollupWorkerFormat = "esm" | "cjs" | "iife" | "umd"Opt-in memory monitor and the always-on `recover()` event-loop yield.
ƒ Functions
process.memoryUsage(). The monitor records snapshot history, emits threshold warnings when
check() is called, and is intended for instrumenting long-running build phases. All thresholds default to safe values when omitted.Parameters
| Name | Type | Description |
|---|---|---|
§options | MemoryMonitorOptions | Optional threshold overrides. Each field defaults to a value appropriate for builder workloads (warning 512 MB, critical 768 MB, growth 50 MB).(default: {}) |
Returns
MemoryMonitorMemoryMonitor with snapshot, check, logDebug, logSummary, and getSnapshots methods.Example
Recording snapshots between phases
const monitor = createMemoryMonitor({ warningMB: 256, criticalMB: 512, growthMB: 32 })
monitor.check('bundle:start')
await runBundlePhase(ctx, config)
monitor.check('bundle:end')
monitor.logSummary()globalThis.gc is available (Node.js started with --expose-gc). This is the always-on free utility companion to the opt-in memory monitor. Call it between memory-heavy phases to drain pending I/O microtasks and reclaim transient allocations before the next phase begins.
Returns
Promise< void>Example
Yielding between heavy build phases
await runBundlePhase(ctx, config)
await recover()
await runPackagePhase(ctx, config)◈ Interfaces
createMemoryMonitor.Properties
check:( label: string) => MemorySnapshotCapture a snapshot and emit warnings when configured thresholds are crossed.logDebug:( label: string) => MemorySnapshotCapture a snapshot and emit a debug-level log line summarizing it.snapshot:( label: string) => MemorySnapshotCapture a snapshot, append it to the history, and return it.Sizes are normalized to megabytes (1 MB = 1024 * 1024 bytes) so callers can compare directly against the configured thresholds.
Properties
Type definitions for builder configuration, context, and results.
◈ Interfaces
Either
files or glob selects the inputs under from. When neither is provided, every file directly under from is copied.Properties
The source file is fixed at
src/bin/<name>.ts. The runner export defaults to the file's default export; override with runner to target a named export instead.Properties
Properties
kind:"cjs" | "esm" | "native"Output kind: a JS script for one of the supported formats, or a native SEA binary.build() facade.Properties
files?:string[ ]Override the published package.json#files allowlist. When omitted, the allowlist is reflected from the materialized output tree after every emit phase: it names exactly what shipped. Provide an explicit array to take full control of what
npm publish ships.filterWorkspaceDepsFromOutput?:booleanDrop workspace-internal entries from the output package.json's dependencies.inheritFieldsFrom?:InheritFromSpecSelectively copy fields from another package.json onto the output package.json.isWorkspacePackage?:IsWorkspacePackagePredicateWorkspace-package predicate; when omitted the bundler treats every dep as external.memoryMonitor?:boolean | MemoryMonitorOptionsEnable the memory monitor; pass true for defaults or an options object for custom thresholds.outputPath?:stringAbsolute output directory. Defaults to <workspaceRoot>/dist/<projectRelativePath>.tsConfig?:stringPath to the project's tsconfig used for declarations. Defaults to <projectRoot>/tsconfig.lib.json.verbose?:booleanRaise the build's log level. When true, the shared logger emits at debug (surfacing every phase's progress, timing, and memory diagnostics); when omitted or false, the build stays quiet at error. Also settable via the hf-build --verbose flag.workspaceDepPolicy?:Record< string, WorkspaceDepHoistPolicy>Per-package override of the workspace-dependency hoist policy, keyed by package name. Packages absent from the map default to 'sub-path' (granular, zero-config); set a package to 'whole-surface' to opt into collapsing its sub-paths onto the root chunk. Builder ships no built-in entries; consumers inject their own opinions here, mirroring isWorkspacePackage.BuildConfig. Unlike
BuildConfig, every path and option here has been resolved to an absolute value and every default has been filled in. Builder primitives consume the BuildContext, never the raw config.Properties
bundledDeps:string[ ]Third-party deps bundled into _dependencies/<dep>/ and stripped from the output package.json. Empty unless at least one format declares bundleAllDeps.isWorkspacePackage:IsWorkspacePackagePredicateWorkspace-package predicate, normalized to always-defined.startedAt:numberWall-clock timestamp captured at context creation, used for BuildResult.durationMs.workspaceBundledDeps:WorkspaceBundledDep[ ]Workspace deps bundled into _dependencies/<packageName>(/<sub>)?/. Empty unless at least one format declares bundleAllDeps and the project declares workspace deps.build() facade.Properties
The default set is
package.json#dependencies minus peerDependencies minus any package matching isWorkspacePackage. include adds packages absent from dependencies; exclude skips ones that would otherwise be picked up. Neither override can resurrect a peer or workspace package.Properties
Properties
bundleAllDeps?:boolean | BundleAllDepsOptionsBundle every third-party dep into _dependencies/<dep>/ and route entry imports through that directory at install-relative paths. When true, builder produces a fully self-contained dist with no dependencies field on the published package.bundleWorkspaceDeps?:booleanInline workspace dependencies (true) or keep them external (false). Defaults to true.entry?:string | string[ ]Entry pattern(s): exact path, glob, or list. Omit to include all detected entries.exports field. Each property maps an export condition (e.g.,
import, require, types) to a resolved file path, or to a nested conditional-export object for further refinement.Properties
Properties
Properties
bundleAllDeps?:boolean | BundleAllDepsOptionsBundle every third-party dep into _dependencies/<dep>/ and route entry imports through that directory at install-relative paths. When true, builder produces a fully self-contained dist with no dependencies field on the published package.bundleWorkspaceDeps?:booleanInline workspace dependencies (true) or keep them external (false). Defaults to true.entry?:string | string[ ]Entry pattern(s): exact path, glob, or list. Omit to include all detected entries.FormatOutputs.Properties
Properties
Properties
Properties
Properties
Properties
Only fields the builder pipeline interacts with directly are typed; arbitrary additional fields are preserved through the index signature and emitted to the output package.json.
Properties
Properties
Properties
Properties
_dependencies/<packageName>(/<sub>)?. The default set is
package.json#dependencies intersected with the workspace predicate.◆ Types
type AssetConditionPredicate = ( pkg: PackageJson) => booleantype BinFormatSpec = BinScriptFormat | BinScriptFormat[ ]type BinScriptFormat = "cjs" | "esm"root: single entry atsrc/index.tsplatform: browser / node split undersrc/browser/andsrc/node/feature: multiple feature modules undersrc/<feature>/hybrid: a mix of root, platform, and / or feature entriescomplex: nested platform-plus-feature structures
type EntryPointCategory = "root" | "platform" | "feature" | "hybrid" | "complex"type EntryPointPlatform = "browser" | "node"exports map entry: a resolved string path, a conditional exports object, or a nested record for sub-conditions.type ExportValue = string | ConditionalExport | Record< string, unknown>Returning
true opts the package into workspace-aware behavior such as inlining during bundling or stripping from the published dependencies map.type IsWorkspacePackagePredicate = ( name: string) => boolean<process.platform>-<process.arch>.type SeaPlatform = "linux-x64" | "linux-arm64" | "darwin-x64" | "darwin-arm64" | "win32-x64"'sub-path' (the zero-config default) gives every public tsconfig specifier of the dep (root and each sub-path) its own _dependencies/<name>(/<sub>)?/index.<ext> chunk, preserving sub-module tree-shaking and reuse, and supporting subpath-only packages that expose no root export. 'whole-surface' is an explicit opt-in collapse: it routes every import of the dep onto a single root chunk, and therefore requires the dep to expose a root export.type WorkspaceDepHoistPolicy = "sub-path" | "whole-surface"Package phase: package.json synthesis, asset copy, and license collection composed via runPackagePhase.
ƒ Functions
package.json#files as the last build step, after every emit phase (bundle → package → bin → license/asset) and the orphan prune have run. The allowlist is reflected from the materialized output tree rather than predicted from config, so the field names exactly what ships. Call this last: earlier phases (notably the bin phase) emit files after the package phase writes the manifest, and reflecting before then would miss those survivors. An explicit
config.files short-circuits reflection and is honored verbatim. An empty allowlist (only possible via an explicit
config.files: []) leaves the manifest untouched.Parameters
| Name | Type | Description |
|---|---|---|
§ctx | BuildContext | Resolved build context (provides outputPath). |
§config | BuildConfig | Top-level builder configuration; reads the files override. |
Example
Finalizing the allowlist after the bin phase
const binOutputs = await runBinPhase(ctx, config.bin ?? [])
finalizeFilesAllowlist(ctx, config)runPackagePhase( ctx: BuildContext, config: BuildConfig, formatOutputs: FormatOutputs): Promise< void>
package.json, materializes any configured asset specs, and (when config.thirdPartyLicenses is enabled, defaulting to true for builds with at least one bundled dep) emits THIRD_PARTY_LICENSES.md. The phase consumes:
- the source
package.json(read fresh fromctx.projectRoot) - the resolved BuildContext (output path, workspace predicate, assets, discovery)
- the FormatOutputs aggregated by the bundle phase
dependencies map only when both config.filterWorkspaceDepsFromOutput and ctx.isWorkspacePackage are present. Inheritance and CDN overrides are forwarded verbatim to synthesizePackageJson.Parameters
| Name | Type | Description |
|---|---|---|
§ctx | BuildContext | Resolved build context. |
§config | BuildConfig | Top-level builder configuration. Reads inheritFieldsFrom, filterWorkspaceDepsFromOutput, unpkg, jsdelivr, bin, and thirdPartyLicenses. The files allowlist is owned by finalizeFilesAllowlist, not this phase. |
§formatOutputs | FormatOutputs | Outputs collected during the bundle phase. |
Example
Driving the package phase from a custom orchestrator
await runPackagePhase(context, config, formatOutputs)Generic, data-driven asset-copy primitive consumed by the package phase. See copyAssets.
ƒ Functions
Each spec selects its inputs in one of two ways:
spec.files: explicit list of relative paths underspec.from. Missing entries
spec.glob: POSIX-style glob evaluated relative tospec.from.
<outputPath>/<spec.to>, defaulting to the dist root when spec.to is omitted or equal to '.'. Specs whose condition predicate returns false are skipped entirely.Parameters
| Name | Type | Description |
|---|---|---|
§specs | AssetSpec[ | Asset specifications to materialize. |
§outputPath | string | Absolute path to the build output directory. |
§srcPkg | PackageJson | Source package.json, supplied to each spec's condition predicate. |
Example
Copying README + LICENSE into the dist root
copyAssets(
[
{ from: '/abs/libs/foo', files: ['README.md', 'CHANGELOG.md'] },
{ from: '/abs/repo', files: ['LICENSE.md', 'SECURITY.md'] },
],
'/abs/dist/libs/foo',
srcPkg
)package.json synthesis primitives: read, inherit, filter, generate exports/CDN paths, and write the dist manifest.
ƒ Functions
PackageJson with bundled-dep entries stripped from dependencies. When the dist contains pre-passed copies under
_dependencies/<dep>/, those packages are no longer transitive runtime requirements: consumers get them inside the tarball. Removing them from the published dependencies field keeps the manifest honest. If filtering empties the
dependencies map, the field is removed entirely.Parameters
| Name | Type | Description |
|---|---|---|
§pkg | PackageJson | Source PackageJson to filter. |
§bundledDeps | string[ | Bundled-dep package names (typically BuildContext.bundledDeps). |
Returns
PackageJsonPackageJson clone.Example
Stripping bundled deps from the published dependencies map
const filtered = filterBundledDepsFromOutput(srcPkg, ['rollup', 'postject'])filterWorkspaceDepsFromOutput( pkg: PackageJson, isWorkspacePackage: IsWorkspacePackagePredicate): PackageJson
PackageJson with workspace-internal entries stripped from dependencies. peerDependencies and optionalDependencies are preserved verbatim so consumers retain optional integrations. If filtering empties the
dependencies map, the field is removed from the returned object instead of left as {}.Parameters
| Name | Type | Description |
|---|---|---|
§pkg | PackageJson | Source PackageJson to filter. |
§isWorkspacePackage | IsWorkspacePackagePredicate | Predicate returning true for workspace-internal packages. |
Returns
PackageJsonPackageJson clone.Example
Stripping `@hyperfrontend/*` deps before publishing
const filtered = filterWorkspaceDepsFromOutput(srcPkg, byPrefix('@hyperfrontend/'))generateExportsFromFormats( discovery: EntryPointDiscovery, formatOutputs: FormatOutputs, srcPkg?: PackageJson): Record< string, ExportValue>
exports field by aligning the source package.json declaration with the actual format outputs the bundle phase produced. Strategy is source-exports first: every key declared on
srcPkg.exports is mapped to a conditional export entry built from the formats that actually landed for the matching subpath. Internal modules that were built but not advertised in the source exports map are intentionally omitted from the published output, and so is a declared key whose source path is not a ./src/<dir>/index.[jt]s or ./src/index.[jt]s module (a file export, a path outside src/, or a conditional with no recognised condition). If
srcPkg has no exports field, falls back to a single root-entry export synthesized from discovery.hasRootEntry. IIFE / UMD CDN bundles are not advertised in
exports; they are reached solely through the unpkg/jsdelivr fields (see getCdnPaths).Parameters
| Name | Type | Description |
|---|---|---|
§discovery | EntryPointDiscovery | Entry-point discovery result produced earlier in the pipeline. |
§formatOutputs | FormatOutputs | Aggregated outputs collected by the bundle phase. |
§srcPkg? | PackageJson | Source package.json whose exports map drives advertised subpaths. |
Returns
Record< string, ExportValue>exports map suitable for the published package.json.Example
Generating exports honoring source aliases
const exports = generateExportsFromFormats(discovery, formatOutputs, srcPkg)package.json. Priority order:
- Explicit
opts.unpkg/opts.jsdelivroverrides win for their respective field. - The first UMD bundle, when one was emitted, supplies the default path.
- The first IIFE bundle is used when no UMD bundle exists.
- When no UMD or IIFE bundles were produced, returns
undefinedso callers can
Parameters
| Name | Type | Description |
|---|---|---|
§formatOutputs | FormatOutputs | Aggregated bundle-phase output. |
§opts? | CdnPathOverrides | Optional explicit overrides for either CDN field. |
Returns
CdnPathsundefined when no CDN-eligible bundle was produced.Example
Resolving CDN paths from a UMD-only build
const cdn = getCdnPaths(formatOutputs)
// → { unpkg: './bundle/index.umd.min.js', jsdelivr: './bundle/index.umd.min.js' }PackageJson with the requested top-level fields copied from the source described by spec onto target. The merge is intentionally shallow: only fields named in
spec.fields are considered, and the original target value wins when the source field is missing or undefined. The function is a no-op (returns target unchanged) when spec is omitted, and silently returns target when the source file does not exist on disk.Parameters
| Name | Type | Description |
|---|---|---|
§target | PackageJson | Package.json being assembled for the published artifact. |
§spec? | InheritFromSpec | Optional inheritance specification: source path + field names to copy. |
Returns
PackageJsonPackageJson with inherited fields applied.Example
Inheriting `repository`, `bugs`, and `author` from the workspace root
const merged = inheritFields(srcPkg, {
from: '/abs/repo/package.json',
fields: ['repository', 'bugs', 'author'],
})package.json from the given project root. The file is required to exist; missing files surface as the standard
FS_NOT_FOUND filesystem error from project-scope.Parameters
| Name | Type | Description |
|---|---|---|
§projectRoot | string | Absolute path to the project root containing package.json. |
Returns
PackageJsonpackage.json contents typed as PackageJson.Example
Reading the source package.json before synthesizing the dist version
const srcPkg = readProjectPackageJson('/abs/libs/foo')
console.log(srcPkg.name)package.json#files allowlist. Derives
files from what the build actually emitted: the two index globs cover every entrypoint index./index.d.ts at any depth, and every surviving non-index file is named explicitly by its package-root-relative POSIX path (no <dir>/ buckets). Run after every emit phase and the orphan-prune so the walk sees exactly the tree that ships; the allowlist is then correct by construction. The root
package.json is skipped (npm always includes it) and index. files are skipped (covered by the glob); metadata files (README, LICENSE, THIRD_PARTY_LICENSES, …) are picked up by the walk only when present. The sorted positive entries are followed by JS_MAP_EXCLUDE_GLOB: a trailing negation that subtracts any JS sourcemap the index glob would otherwise ship. It must come last (npm resolves
files last-match-wins), so it is appended after the sort rather than folded into the sorted set.Parameters
| Name | Type | Description |
|---|---|---|
§outputPath | string | Absolute path to the materialized publishable output root. |
Returns
string[ ]package.json#files.Example
Reflecting a built library's output tree
const files = reflectFilesAllowlist(ctx.outputPath)
// => the two index globs, then 'README.md', 'models.d.ts', 'bin/hf-build.js', …, '!**/*.js.map'synthesizePackageJson( srcPkg: PackageJson, ctx: BuildContext, formatOutputs: FormatOutputs, opts?: SynthesizePackageJsonOptions): PackageJson
package.json from the source manifest, the bundle-phase outputs, and caller-supplied options. Pipeline order is: filter workspace deps → strip the source-only fields (
scripts, devDependencies, packageManager, type) → apply inherited fields → assemble the new manifest with sideEffects: false, the regenerated exports map, and resolved main / module / types pointers; CDN fields are appended only when a UMD or IIFE bundle was emitted; the bin field is synthesized from the supplied bin declarations using deterministic naming rules; the files allowlist is forwarded verbatim when supplied. Root-pointers (
main, module, types) are removed entirely when the library has no root entry, so consumers cannot resolve a non-existent default.Parameters
| Name | Type | Description |
|---|---|---|
§srcPkg | PackageJson | Source package.json parsed from the project root. |
§ctx | BuildContext | Resolved build context (used for the entry-point discovery shape). |
§formatOutputs | FormatOutputs | Aggregated outputs collected by the bundle phase. |
§opts? | SynthesizePackageJsonOptions | Inheritance / filter / CDN-override / bin / files toggles. |
Returns
PackageJsonPackageJson ready to be written to the dist root.Example
Synthesizing the dist package.json for a library with workspace inlining
const distPkg = synthesizePackageJson(srcPkg, ctx, formatOutputs, {
inheritFieldsFrom: { from: '/abs/repo/package.json', fields: ['repository', 'bugs'] },
filterWorkspaceDepsFromOutput: true,
isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/'),
bins: [{ name: 'hf-build', format: ['cjs'] }],
files: ['_dependencies/', 'bin/', 'index.*'],
})package.json to the build output directory. The file is emitted with two-space indentation (project-scope's default) and a trailing newline so the published artifact diffs cleanly against formatter-managed source files.
Parameters
| Name | Type | Description |
|---|---|---|
§outputPath | string | Absolute path to the output directory (the dist root). |
§packageJson | PackageJson | Fully assembled PackageJson to serialize. |
Example
Writing the dist package.json
writeOutputPackageJson(ctx.outputPath, distPkg)◈ Interfaces
Properties
unpkg and jsdelivr package.json fields.Properties
Properties
filterWorkspaceDepsFromOutput?:booleanStrip workspace-internal entries from the output dependencies map.inheritFieldsFrom?:InheritFromSpecSelectively copy fields from another package.json onto the output.isWorkspacePackage?:IsWorkspacePackagePredicatePredicate identifying workspace-internal packages; required when filtering is enabled.Opt-in third-party license collection that writes `THIRD_PARTY_LICENSES.md` to the dist root.
ƒ Functions
collectThirdPartyLicenses( workspaceRoot: string, externals: string[ ]): ThirdPartyLicenseEntry[ ]
The dependency list is supplied by the caller (typically the resolved external list from the bundle phase, with workspace-internal entries already removed). For each dependency, the function locates the installed package under
<workspaceRoot>/node_modules, parses its package.json, scans for a LICENSE file, and attempts to derive a canonical license URL from the upstream repository field. Missing dependencies are logged at warn level and dropped from the result.Parameters
Returns
ThirdPartyLicenseEntry[ ]Example
Collecting licenses for a built library's bundle phase externals
const entries = collectThirdPartyLicenses('/abs/repo', ['rollup', 'typescript'])THIRD_PARTY_LICENSES.md from a collected list of license entries. The output is a top-level heading followed by a two-column table mapping each dependency to a markdown link pointing at the upstream license file (or the raw license type when no URL is available). The body always ends with a trailing newline.
Parameters
| Name | Type | Description |
|---|---|---|
§entries | ThirdPartyLicenseEntry[ | License entries produced by collectThirdPartyLicenses. |
Returns
stringExample
Generating the markdown body from a license collection
const md = generateThirdPartyLicensesContent(entries)<outputPath>/THIRD_PARTY_LICENSES.md. Parent directories are created if necessary by the underlying project-scope writer.Parameters
Example
Writing the file at the dist root
writeThirdPartyLicensesFile(ctx.outputPath, content)◆ Types
Predicate factories for the IsWorkspacePackagePredicate slot on BuildConfig, opting a build into workspace-aware behavior.
ƒ Functions
Use this preset when the workspace exposes packages under heterogeneous scopes (or unscoped names) and a single string prefix can't capture them all.
Parameters
| Name | Type | Description |
|---|---|---|
§names | string[ | Exact package names to treat as workspace-internal. |
Returns
IsWorkspacePackagePredicatetrue when the supplied name appears in names.Example
Tagging an explicit set of workspace packages
const isWorkspacePackage = byNames(['@hyperfrontend/logging', 'internal-utils'])
isWorkspacePackage('internal-utils') // => true
isWorkspacePackage('rollup') // => falseThe scope is treated as a literal string prefix: pass the full scope including the trailing slash (
'@hyperfrontend/') when matching scoped packages so the predicate doesn't accidentally treat @hyperfrontend-foo/x as a workspace package.Parameters
| Name | Type | Description |
|---|---|---|
§scope | string | Literal prefix to match against package names. |
Returns
IsWorkspacePackagePredicatetrue when the supplied name starts with scope.Example
Matching every workspace package by scope
const isWorkspacePackage = byPrefix('@hyperfrontend/')
isWorkspacePackage('@hyperfrontend/logging') // => true
isWorkspacePackage('rollup') // => falseRelated reading§
- Architecture
Architecture
How @hyperfrontend/builder is put together, and why.
- Tutorial
Publish a TypeScript library to npm
Shipping a TypeScript library means an exports map, ESM and CJS builds, and a declaration pass that all have to agree, and every one of them is hand-maintained until it silently stops matching what was emitted.
- Getting started
Getting Started
Set HyperFrontend up and embed a first feature.
- Architecture
Architecture Guide
How the packages fit together.

