@hyperfrontend/builder/bundle/dedupe

dedupe

Shared-first-party dedupe: an additive post-emit pass that lifts first-party modules inlined into multiple per-entry bundles into shared _shared/<srcPath>/index.<fmt>.js chunks and rewrites each consuming entry to import them. Runs over the already-bundled esm/cjs outputs only, leaving per-entry isolated bundling intact.

hoistSharedFirstParty(...) is the entry point. It only hoists copies that planHoists proves safe — structurally identical, references resolvable, and initialization cycle-free — so the worst case is output identical to the input.

The pass is built from composable stages, each exported for direct use: module attribution (attribute, indexOwners, parseEntry), chunk planning and extraction (planHoists, renderChunk, resolveModuleRefs), and entry rewriting (rewriteEntry).

API Reference

ƒ Functions

§function

attribute(parsed: ParsedEntry, owners: OwnerIndex): Map<string, EntryDecl[]>

Groups an entry's attributable declarations by their owning module.
Declarations whose base name is unowned (a dependency or unexported helper inlined into the bundle) are skipped, leaving them in place.

Parameters

NameTypeDescription
§parsed
ParsedEntry
A parsed entry bundle.
§owners
OwnerIndex
The first-party ownership index.

Returns

Map<string, EntryDecl[]>
Map from module key to that module's declarations in source order.

Example

Attributing an entry's declarations

const byModule = attribute(parseEntry(source, 'esm'), owners)
§function

baseName(name: string): string

Strips rollup's $N collision-rename suffix from a local name, yielding the canonical source symbol name.

Parameters

NameTypeDescription
§name
string
A local identifier from an emitted bundle.

Returns

string
The base name with any trailing $digits removed.

Example

Undoing a collision rename

baseName('Store$1') // => 'Store'
§function

chunkFileName(format: ChunkFormat): string

Resolves a hoisted module's chunk file name for a format.

Parameters

NameTypeDescription
§format
ChunkFormat
Output module format.

Returns

string
The per-entry chunk file name (index.esm.js / index.cjs.js).

Example

Naming the CJS chunk

chunkFileName('cjs') // => 'index.cjs.js'
§function

fingerprintOf(statement: Statement, sourceFile: SourceFile): string

Computes a rename-insensitive identity key for a top-level statement.
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

NameTypeDescription
§statement
Statement
The top-level statement to fingerprint.
§sourceFile
SourceFile
The source file the statement belongs to.

Returns

string
The statement's text with $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` copies
§function

hoistSharedFirstParty(context: BuildContext, monitor?: MemoryMonitor): HoistReport

Lifts first-party modules that are inlined into multiple entry bundles into shared _shared/<srcPath>/index.<fmt>.js chunks and rewrites every consuming entry to import them.
Runs as an additive post-emit pass over the already-bundled esm and cjs outputs, each processed independently; iife/umd/bin and .d.ts outputs are never read. Per-entry isolated bundling is untouched. Every hoist is proven safe by planHoists before it happens — structurally identical copies, resolvable references, and cycle-free module-initialization — so any module that cannot be proven safe is left inlined and the output is, worst case, identical to the input.

Parameters

NameTypeDescription
§context
BuildContext
Resolved build context. outputPath locates the bundles and projectRoot/src drives ownership attribution.
§monitor?
MemoryMonitor
Optional memory monitor; a checkpoint is captured after each format so the pass is observable.

Returns

HoistReport
Counts of chunks written and net bytes reclaimed.

Example

Hoisting after the dependency prune

const report = hoistSharedFirstParty(context, monitor)
§function

indexOwners(srcRoot: string): OwnerIndex

Builds the first-party ownership index by scanning <srcRoot>/** for the runtime symbols each module exports.
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

NameTypeDescription
§srcRoot
string
Absolute path to the project's src/ directory.

Returns

OwnerIndex
The ownership index.

Example

Indexing a library's source tree

const owners = indexOwners('/abs/libs/foo/src')
§function

parseEntry(source: string, format: ChunkFormat): ParsedEntry

Parses one entry bundle into the structural model the hoist pass consumes.
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

NameTypeDescription
§source
string
Raw entry bundle source text.
§format
ChunkFormat
Module format selecting ESM vs CJS import/export shapes.

Returns

ParsedEntry
The parsed entry model.

Example

Modeling an ESM entry bundle

const parsed = parseEntry("import { x } from './_dependencies/a/index.esm.js'\nclass C {}", 'esm')
§function

planHoists(entries: EntryInput[], owners: OwnerIndex): Map<string, PlannedModule>

Plans which first-party modules can be hoisted into shared chunks.
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

NameTypeDescription
§entries
EntryInput[]
Parsed entries with their per-module attribution.
§owners
OwnerIndex
First-party ownership index.

Returns

Map<string, PlannedModule>
Map from module key to its hoist plan.

Example

Planning hoists for a set of entries

const plan = planHoists(entries, owners)
§function

renderChunk(plan: ChunkPlan, format: ChunkFormat): string

Renders a hoisted module chunk: its resolved imports, the union of its declarations, and an export surface naming each declaration by its base name.

Parameters

NameTypeDescription
§plan
ChunkPlan
The module's declarations plus resolved import edges.
§format
ChunkFormat
Output module format.

Returns

string
The chunk source text.

Example

Rendering an ESM chunk

const source = renderChunk({ decls, crossImports: [], depImports: [] }, 'esm')
§function

resolveModuleRefs(decls: EntryDecl[], owners: OwnerIndex, importBindings: Map<string, ImportBinding>, entryDeclNames: Set<string>, selfModuleKey: string): ModuleResolution

Classifies every free identifier referenced by a module's declarations, partitioning them into cross-module edges, dependency edges, and hard blockers.
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

NameTypeDescription
§decls
EntryDecl[]
The module's canonical declarations.
§owners
OwnerIndex
First-party ownership index.
§importBindings
Map<string, ImportBinding>
The consuming entry's import bindings.
§entryDeclNames
Set<string>
Every top-level declaration name in the entry.
§selfModuleKey
string
The module being resolved.

Returns

ModuleResolution
The reference resolution.

Example

Resolving a module's references

const resolution = resolveModuleRefs(decls, owners, parsed.importBindings, parsed.declNames, 'events/events')
§function

rewriteEntry(parsed: ParsedEntry, hoists: EntryHoist[], format: ChunkFormat): string

Rewrites an entry bundle to consume hoisted modules from _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

NameTypeDescription
§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

string
The rewritten entry source, or the original source when hoists 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')
§function

sharedDirFor(moduleKey: string): string

Computes the _shared/ directory (relative to the output root) that holds a module's hoisted chunk.

Parameters

NameTypeDescription
§moduleKey
string
The owning module's key.

Returns

string
The forward-slashed _shared/<moduleKey> directory.

Example

Locating a module's shared directory

sharedDirFor('events/events') // => '_shared/events/events'

Interfaces

§interface

ChunkPlan

Everything needed to render a single module's hoisted chunk.

Properties

§crossImports:CrossImport[]
Resolved cross-module import edges.
§decls:EntryDecl[]
The module's declarations in canonical source order.
§depImports:DepImport[]
Resolved dependency import edges.
§interface

CrossImport

A cross-module import edge resolved to a concrete chunk specifier.

Properties

§exported:string
Exported name on the target chunk.
§ref:string
Local binding name in the emitted chunk.
§specifier:string
Module specifier reaching the target chunk from this chunk.
§interface

CrossRef

A reference from a module's declarations into another first-party module's hoisted chunk.

Properties

§base:string
The referenced symbol's base name — the name the target chunk exports.
§moduleKey:string
Owning module key of the referenced symbol.
§ref:string
Local identifier as written in this module's code (may carry a $N suffix).
§interface

DepImport

A dependency import edge resolved to a concrete (chunk-relative) specifier.

Properties

§imported?:string
Exported name for named/cjs-named bindings.
§kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"
Binding shape governing how the import is re-emitted.
§ref:string
Local binding name in the emitted chunk.
§specifier:string
Chunk-relative module specifier reaching the dependency.
§interface

DepRef

A reference from a module's declarations into a dependency import the entry already carries.

Properties

§binding:ImportBinding
The entry's binding shape for that identifier.
§ref:string
Local identifier as written in this module's code.
§interface

EntryDecl

A removable top-level runtime declaration in an entry bundle, attributed to its owning first-party module by name.

Properties

§base:string
Owning symbol's base name (collision suffix stripped).
§fingerprint:string
Rename-insensitive identity key — $N suffixes stripped from identifier nodes; the raw text is still the chunk-body source.
§localName:string
Local name exactly as the bundle declares it (may carry a $N suffix).
§statement:Statement
The owning top-level statement.
§text:string
Emitted code text (leading comments excluded); the raw chunk-body source.
§interface

EntryHoist

One module's hoist within a single entry: the declarations to splice out and the chunk specifier the entry should import them from.

Properties

§decls:EntryDecl[]
The module's declarations as they appear in this entry.
§specifier:string
Chunk-relative module specifier reaching the module's shared chunk.
§interface

EntryInput

One parsed entry bundle paired with its declarations grouped by owning module.

Properties

§byModule:Map<string, EntryDecl[]>
The entry's attributable declarations grouped by module key.
§parsed:ParsedEntry
The parsed entry bundle.
§interface

HoistReport

Aggregate result of the shared-first-party hoist pass.

Properties

§bytesReclaimed:number
Net bytes reclaimed: inlined copies removed minus the shared chunks emitted.
§chunksWritten:number
Number of _shared/ chunk files written across all formats.
§interface

ImportBinding

A single import binding observed at an entry bundle's top level, retaining enough shape to re-materialize the same edge from a hoisted chunk.

Properties

§imported?:string
Exported name on the target module for named/cjs-named bindings.
§kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"
Shape of the binding, governing how it is re-emitted into a shared chunk.
§specifier:string
Raw module specifier exactly as written in the entry source.
§interface

ModuleResolution

Raw classification of every free identifier a module's declarations reference.

Properties

§crossModule:CrossRef[]
References into other first-party modules.
§depImports:DepRef[]
References into dependency imports the entry carries.
§unresolved:string[]
References that name an inlined, unattributable top-level entry binding — a hard blocker.
§interface

OwnerIndex

Ownership index mapping each unambiguously-owned runtime symbol to the first-party module that declares it.

Properties

§ownerOf:Map<string, string>
Maps a runtime symbol's base name (rollup $N collision suffix stripped) to its owning module key. Names declared by more than one module are omitted — an ambiguous name can never be safely attributed.
§interface

ParsedEntry

Structural model of one parsed entry bundle.

Properties

§bareStatements:Statement[]
Top-level statements that are neither imports, declarations, nor the export surface.
§declNames:Set<string>
Every local name a top-level declaration binds.
§decls:EntryDecl[]
Top-level runtime declarations in source order.
§headerEnd:number
End offset of the bundle's leading import/require/'use strict' header — the insertion point for hoisted imports.
§importBindings:Map<string, ImportBinding>
Import/require bindings keyed by their local name.
§sourceFile:SourceFile
The parsed source file.
§interface

PlannedModule

A module the plan will hoist into a shared chunk.

Properties

§canonicalEntryIndex:number
Index of the entry whose copy defines the canonical chunk content and order.
§consumingEntryIndexes:number[]
Indexes of every entry that inlines this module and must be rewritten.
§decls:EntryDecl[]
Canonical declarations, in source order, that populate the chunk.
§resolution:ModuleResolution
Resolved references the chunk must re-import.

Types

§type

ModuleKey

A first-party module's path under 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 = string