@hyperfrontend/builder/bundle/dependenciesdependencies
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
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
Returns
string[]Example
Resolving bundled deps for a workspace library
const deps = resolveBundledDeps('/abs/libs/foo/package.json', {
isWorkspacePackage: (n) => n.startsWith('@hyperfrontend/'),
})@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/bundle/dependencies/worker/index.cjs.js<workspaceRoot>/node_modules/@hyperfrontend/builder/bundle/dependencies/worker/index.cjs.js<workspaceRoot>/libs/builder/src/bundle/dependencies/worker/index.ts(with--require \@swc-node/register)
Parameters
| Name | Type | Description |
|---|---|---|
§workspaceRoot | string | Absolute workspace root. |
Returns
WorkerInvocationundefined 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')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
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
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
depsRoot?:string— _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.kind:PrePassJobKind— js and dts cover npm bundled deps; workspace-js and workspace-dts cover workspace @hyperfrontend/* deps whose entries are TypeScript source.npmDeps?:string[]— otherDeps:string[]— otherWorkspaceSpecifiers?:string[]— workspace-* jobs so sibling sub-paths externalize cleanly (e.g., one built-in-copy/<x> chunk does not pull in another).selfDtsPath?:string— siblingEntries?:SiblingEntryDescriptor[]— dts pass to externalize imports that resolve into another entry's directory. See SiblingEntryDescriptor. Empty / omitted for dep pre-pass jobs.workspaceRoot?:string— baseUrl for path-mapping resolution (workspace-* jobs only).workspaceRoutes?:WorkspaceBundledDepRoute[]— workspace-js / workspace-dts) this excludes the specifier or package being built so the chunk inlines its own internals.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?:string— _dependencies/ root. Required when npmDeps or workspaceRoutes is non-empty.format:"cjs" | "esm"— 'esm' or 'cjs'. dts jobs always use 'es' internally.inputPath:string— .ts for workspace-*).kind:PrePassWorkerJobKind— 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[]— otherDeps:string[]— otherWorkspaceSpecifiers?:string[]— @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?:string— selfSrcPath?:string— srcPath (used for diagnostics). Empty string for the package root.siblingEntries?:SiblingEntry[]— dts pass to externalize imports that resolve into another entry's directory. Empty / omitted for dep pre-pass jobs.workspaceRoot?:string— baseUrl for path-mapping resolution (workspace-* jobs only).workspaceRoutes?:WorkspaceBundledDepRoute[]— 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
policy:WorkspaceDepHoistPolicy— specifier:string— <packageName> or <packageName>/<subPath>.tsConfigPath:string— @rollup/plugin-typescript during pre-pass.Properties
policy?:Record<string, WorkspaceDepHoistPolicy>— '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.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
◆ Types
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"