@hyperfrontend/builder§

Two source entries feed the builder one at a time; each fans out into its ESM, CJS and declaration files, the root entry also into the minified IIFE and UMD bundles; then a package.json sheet rises and wires connect every file that landed to the exports, main, types and files keys it producedTwo source entries feed the builder one at a time; each fans out into its ESM, CJS and declaration files, the root entry also into the minified IIFE and UMD bundles; then a package.json sheet rises and wires connect every file that landed to the exports, main, types and files keys it produced

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

  • Multi-format output

    emit ESM, CJS, IIFE, and UMD bundles from one config; omit a format to skip it.

  • Bins & native binaries

    synthesize JavaScript bins and cross-platform Node SEA native executables.

  • Per-entry isolation

    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.

  • Self-contained packages

    bundle first-party and third-party dependencies, with an additive post-emit pass that dedups shared internals into _shared/ chunks.

  • Composable phases

    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-build CLI) 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§

The architecture guide covers the phase pipeline, the per-entry worker model, and the shared-internals dedup pass.

API Reference§

View:
Organized by entry point

Module Structure

|

21 modules · 247 total exports

Composable, vendor-neutral build toolkit for TypeScript libraries, JS bins, and Node SEA native binaries.

9 fn30 int9 type

JS bin synthesis, composed via runBinPhase.

1 fn

Node SEA native binary primitives: config generation, blob prep, host resolution, postject injection, and macOS code-sign cleanup.

11 fn14 int3 var

Forked-worker entry script that runs a single postject inject via runInjectWorkerJob, isolating its ~138 MB buffer from the parent RSS.

1 fn2 int

JS bin synthesis primitives: rollup-driven bundling with shebang + bootstrap footer + chmod.

2 fn1 int

Bundle phase orchestrator: entry discovery, externals, Rollup, and declaration emission, run in order via runBundlePhase.

1 fn1 int

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.

14 fn5 int

Additive post-emit pass that hoists first-party modules inlined into multiple per-entry bundles into shared chunks. See hoistSharedFirstParty.

13 fn14 int1 type

Per-format pre-pass and externalize plugin that bundle each third-party (and workspace) dep once, then reroute every entry's import to it.

9 fn10 int3 type

Forked-worker entry script that runs a single dependency pre-pass via runPrePassWorkerJob.

1 fn2 int

Entry-point discovery, resolution, and platform filtering primitives.

4 fn

Externals resolution primitives: package.json scanning and globals validation for IIFE / UMD bundles.

2 fn1 int

Rollup driver: per-format descriptor builders and the per-entry forked-worker dispatcher.

8 fn6 int1 type

Forked-worker entry script that runs a single per-entry rollup pass via runRollupWorkerJob.

1 fn4 int1 type

Opt-in memory monitor and the always-on `recover()` event-loop yield.

2 fn2 int

Type definitions for builder configuration, context, and results.

28 int9 type

Package phase: package.json synthesis, asset copy, and license collection composed via runPackagePhase.

2 fn

Generic, data-driven asset-copy primitive consumed by the package phase. See copyAssets.

1 fn

package.json synthesis primitives: read, inherit, filter, generate exports/CDN paths, and write the dist manifest.

9 fn3 int

Opt-in third-party license collection that writes `THIRD_PARTY_LICENSES.md` to the dist root.

3 fn1 type

Predicate factories for the IsWorkspacePackagePredicate slot on BuildConfig, opting a build into workspace-aware behavior.

2 fn

Browse guides filtered to this packageSuggest a guide