@hyperfrontend/builder/bundle/dependencies

dependencies

Per-format pre-pass + externalize plugin that build each third-party dep once into _dependencies/<dep>/, then route every entry's import of that dep through a relative path. Locked per Decision #38 (overview).

resolveBundledDeps(packageJsonPath, { isWorkspacePackage, include, exclude }) derives the third-party dep set from the project's package.json#dependencies, subtracts peerDependencies (those stay external) and any package matching the workspace predicate (those are inlined per the existing bundleWorkspaceDeps flow), and finally applies the caller's include / exclude overrides — exclude always wins over include, and neither can resurrect a peer or workspace dep.

createExternalizeBundledDepsPlugin({ deps, entryOutDir, format, depsRoot }) returns a Rollup plugin whose resolveId hook maps any import of a bundled dep (or its subpath) to a relative import that points at _dependencies/<dep>/index.<ext>. Node builtins and node:* specifiers are marked external untouched. Unrelated imports return null so the rest of the plugin chain handles them. Used by the per-entry passes for 'esm' / 'cjs' and by the d.ts pass for 'dts'.

runPrePass(jobs, { workerPath, monitor }) orchestrates the per-dep × per-format rollup invocations as forked Node child processes (Decision #39). Each child runs exactly one rollup invocation, writes its output, and emits a JSON report. Strictly sequential — concurrent workers would simultaneously pressure RAM. The orchestrator throws with the failed job's context if any worker exits non-zero or fails to write its report.

API Reference

ƒ Functions

§function

buildWorkspaceRoutes(entries: WorkspaceBundledDep[]): WorkspaceBundledDepRoute[]

Groups 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

NameTypeDescription
§entries
WorkspaceBundledDep[]
Workspace bundled-dep entries from BuildContext.workspaceBundledDeps.

Returns

WorkspaceBundledDepRoute[]
Routes ready to feed into the externalize plugin.

Example

Building plugin routes from the build context

const routes = buildWorkspaceRoutes(context.workspaceBundledDeps)
createExternalizeBundledDepsPlugin({ deps, entryOutDir, format, depsRoot, workspaceRoutes: routes })
§function

createExternalizeBundledDepsPlugin(options: ExternalizeBundledDepsPluginOptions): Plugin

Returns a rollup plugin whose 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

NameTypeDescription
§options
ExternalizeBundledDepsPluginOptions
Plugin configuration.

Returns

Plugin
Rollup plugin.

Example

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',
})
§function

loadWorkspacePathMappings(workspaceRoot: string): Map<string, string[]>

Loads tsconfig path-mappings from a workspace, preferring tsconfig.base.json at the workspace root and falling back to a tsconfig.json extends chain when the base form is absent.

Parameters

NameTypeDescription
§workspaceRoot
string
Absolute workspace root.

Returns

Map<string, string[]>
Map keyed by tsconfig 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']
§function

relativeImport(fromDir: string, toFile: string): string

Computes the relative import specifier that a rollup output at 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

NameTypeDescription
§fromDir
string
Absolute directory containing the output file.
§toFile
string
Absolute path to the target file.

Returns

string
Relative import specifier ready for inclusion in emitted code.

Example

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'
§function

resolveBundledDeps(packageJsonPath: string, options: ResolveBundledDepsOptions): string[]

Resolves the list of third-party dependencies that should be pre-passed (bundled once into _dependencies/<dep>/) for the build.
Algorithm:
  1. Read dependencies from the project's package.json.
  2. Subtract peerDependencies — those stay external.
  3. Subtract anything matching isWorkspacePackage — workspace deps are inlined per the existing flow.
  4. Apply include / exclude overrides.
The returned list is sorted and de-duplicated.

Parameters

NameTypeDescription
§packageJsonPath
string
Absolute path to the project's package.json.
§options
ResolveBundledDepsOptions
Caller overrides.
(default: {})

Returns

string[]
Sorted list of third-party package names to pre-pass.

Example

Resolving bundled deps for a workspace library

const deps = resolveBundledDeps('/abs/libs/foo/package.json', {
  isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/'),
})
§function

resolveDefaultWorkerPath(workspaceRoot: string): WorkerInvocation

Default worker-path resolution: prefers the built-and-published artifact, falls back to the workspace dist path, and finally to the in-source TypeScript file via @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:
  1. <workspaceRoot>/dist/libs/builder/bundle/dependencies/worker/index.cjs.js
  2. <workspaceRoot>/node_modules/@hyperfrontend/builder/bundle/dependencies/worker/index.cjs.js
  3. <workspaceRoot>/libs/builder/src/bundle/dependencies/worker/index.ts (with --require \@swc-node/register)

Parameters

NameTypeDescription
§workspaceRoot
string
Absolute workspace root.

Returns

WorkerInvocation
Worker invocation descriptor, or undefined if no candidate exists.

Example

Locating the worker for an in-workspace consumer

const invocation = resolveDefaultWorkerPath('/abs/repo')
if (!invocation) throw new Error('builder worker artifact not found')
§function

resolveWorkspaceBundledDeps(packageJsonPath: string, workspaceRoot: string, options: ResolveWorkspaceBundledDepsOptions): ResolvedWorkspaceDepEntry[]

Resolves the list of workspace @hyperfrontend/* deps that should be hoisted into _dependencies/<name>(/<sub>)?/index.<ext>.js.
Algorithm:
  1. 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.
  1. Load workspace path-mappings (tsconfig paths).
  2. 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
tsconfig specifier (root + every sub-path with a real source file), preserving sub-module granularity and supporting subpath-only packages that expose no root export.
  • 'whole-surface' is an explicit opt-in collapse: it emits a single entry
for the package root, so it requires the dep to expose a root export.
  1. The returned list is sorted by specifier for stable downstream ordering.
Because the caller requested 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

NameTypeDescription
§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[]
Resolved workspace-dep pre-pass entries sorted by specifier.

Example

Resolving workspace pre-pass entries for builder

const entries = resolveWorkspaceBundledDeps(
  '/abs/libs/builder/package.json',
  '/abs/repo',
  { isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/') }
)
§function

runPrePass(jobs: PrePassJob[], options: RunPrePassOptions): Promise<PrePassResult[]>

Sequentially runs the supplied pre-pass jobs by forking a fresh Node child per invocation. Strict sequential execution is mandatory — concurrent children would simultaneously pressure RAM and OOM the container.
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

NameTypeDescription
§jobs
PrePassJob[]
Pre-pass jobs to run.
§options
RunPrePassOptions
Worker path + optional memory monitor.

Returns

Promise<PrePassResult[]>
One result per supplied job, in the order the jobs were given.

Example

Pre-passing rollup and one of its plugins

const results = await runPrePass(jobs, { workerPath: '/abs/dist/libs/builder/bundle/dependencies/worker.cjs.js' })
§function

runPrePassWorkerJob(job: PrePassWorkerJob): Promise<PrePassWorkerReport>

Runs a single pre-pass rollup invocation as described by 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

NameTypeDescription
§job
PrePassWorkerJob
Job spec describing the rollup invocation.

Returns

Promise<PrePassWorkerReport>
The on-disk report data the worker would have persisted.

Example

Driving the worker logic in-process for a fixture

const report = await runPrePassWorkerJob({ kind: 'js', dep: 'rollup', ... })

Interfaces

§interface

ExternalizeBundledDepsPluginOptions

Inputs to createExternalizeBundledDepsPlugin.

Properties

§deps:string[]
Bundled-dep package names. Any import of these (or their subpaths) is rerouted to _dependencies/<dep>/.
§depsRoot:string
Absolute path to the _dependencies/ root.
§entryOutDir:string
Absolute directory the rollup invocation writes its output to.
§format:ExternalizeFormat
Format being produced by this rollup invocation.
§workspaceRoutes?:WorkspaceBundledDepRoute[]
Workspace bundled-dep routes. Imports of these packages reroute to _dependencies/<packageName>(/<sub>)?/.
§interface

PrePassJob

Single-rollup-invocation job description fed to a forked worker.

Properties

§dep:string
Dep package name (or workspace specifier), e.g. "rollup" or "@hyperfrontend/logging".
§depsRoot?:string
Absolute path to the project's _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.
§format:"cjs" | "esm"
Format produced by this rollup invocation.
§inputPath:string
Absolute path to the dep's input entry.
§kind:PrePassJobKind
Pre-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).
§outputPath:string
Absolute path to the worker's output file.
§selfDtsPath?:string
Absolute path to the input file's owning entry directory (used to compute sibling specifiers).
§selfSrcPath?:string
Owning entry's srcPath. Empty string for the package root.
§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.
§tsConfigPath?:string
Absolute path to the project's tsconfig (workspace-* jobs only).
§workspaceRoot?:string
Absolute 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.
§interface

PrePassResult

Result emitted by the worker for a single job.

Properties

§durationMs:number
Wall-clock duration the worker reported, in ms.
§endHeapMB:number
Heap reported by the child at exit, in MB.
§endRssMB:number
RSS reported by the child at exit, in MB.
§job:PrePassJob
The job that produced this result.
§outputSize:number
Output file size in bytes.
§interface

PrePassWorkerJob

Job spec consumed by the pre-pass worker via 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

§dep:string
Dep package name (or workspace specifier) being pre-passed.
§depsRoot?:string
Absolute 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:string
Absolute path to the dep's entry (main / module for JS, types for dts, source .ts for workspace-*).
§kind:PrePassWorkerJobKind
Pre-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.
§outputPath:string
Absolute path to the output file.
§reportPath:string
Absolute path where the worker writes its JSON report.
§selfDtsPath?:string
Absolute path to the input file's owning entry directory (used to compute sibling specifiers).
§selfSrcPath?:string
Owning 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.
§tsConfigPath?:string
Absolute path to the project's tsconfig (workspace-* jobs only).
§workspaceRoot?:string
Absolute 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.
§interface

PrePassWorkerReport

Memory + size summary written to reportPath when a worker exits cleanly.

Properties

§durationMs:number
Worker wall-clock duration in ms.
§endHeapMB:number
process.memoryUsage().heapUsed in MB at end of bundle.
§endRssMB:number
process.memoryUsage().rss in MB at end of bundle.
§outputSize:number
Final on-disk size of the rollup output, in bytes.
§interface

ResolveBundledDepsOptions

Inputs to resolveBundledDeps.

Properties

§exclude?:string[]
Skip these packages even if otherwise selected.
§include?:string[]
Force-include packages even if absent from package.json#dependencies.
§isWorkspacePackage?:IsWorkspacePackagePredicate
Predicate identifying workspace-internal packages; when matched, packages are NOT pre-passed.
§interface

ResolvedWorkspaceDepEntry

Resolved workspace-dep pre-pass entry.

Properties

§inputPath:string
Absolute path to the source file (from tsconfig path-mapping).
§packageName:string
Workspace package name, e.g. @hyperfrontend/logging.
§policy:WorkspaceDepHoistPolicy
Hoist policy applied to this entry's package; carried through so callers need not re-derive it.
§specifier:string
Public import specifier this entry resolves: <packageName> or <packageName>/<subPath>.
§subPath:string
Sub-path under the package; '' when the entry is the package root.
§tsConfigPath:string
Absolute path to the dep's own tsconfig used by @rollup/plugin-typescript during pre-pass.
§interface

ResolveWorkspaceBundledDepsOptions

Inputs to resolveWorkspaceBundledDeps.

Properties

§exclude?:string[]
Skip these packages even if otherwise selected.
§include?:string[]
Force-include packages even if absent from package.json#dependencies.
§isWorkspacePackage:IsWorkspacePackagePredicate
Predicate identifying workspace-internal packages.
§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.
§interface

RunPrePassOptions

Caller-supplied options threaded through runPrePass.

Properties

§execArgv?:string[]
Extra arguments prepended to the worker invocation (e.g. ['--require', '@swc-node/register']).
§execPath?:string
Override process.execPath for the spawned worker (used by tests).
§monitor?:MemoryMonitor
Optional memory monitor invoked between every spawned worker.
§workerPath:string
Absolute path to the worker entry script. Required — see resolveDefaultWorkerPath.
§interface

WorkspaceBundledDepRoute

Per-package workspace-dep routing entry.
  • policy: 'whole-surface' collapses every import of <packageName> (root or
sub-path) onto the single root chunk at _dependencies/<packageName>/index.<ext>.
  • policy: 'sub-path' matches each pre-passed specifier exactly; non-matched
sub-path imports of the same package fall through unchanged so the rest of the plugin chain can resolve / inline them.

Properties

§packageName:string
Workspace package name (e.g. @hyperfrontend/logging).
§policy:"sub-path" | "whole-surface"
Routing policy applied to imports of this package.
§specifiers?:string[]
When policy === 'sub-path': the exact specifiers that were pre-passed.

Types

§type

ExternalizeFormat

Format produced by the rollup invocation that uses the plugin.
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"
§type

PrePassJobKind

Pre-pass kind. Mirrors PrePassWorkerJobKind on the worker side.
type PrePassJobKind = "js" | "dts" | "workspace-js" | "workspace-dts"