@hyperfrontend/builder/bundle/dedupededupe
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
Declarations whose base name is unowned (a dependency or unexported helper inlined into the bundle) are skipped, leaving them in place.
Parameters
Returns
Map<string, EntryDecl[]>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'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` copies<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
| Name | Type | Description |
|---|---|---|
§srcRoot | string | Absolute path to the project's src/ directory. |
Returns
OwnerIndexExample
Indexing a library's source tree
const owners = indexOwners('/abs/libs/foo/src')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
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
Returns
Map<string, PlannedModule>Example
Planning hoists for a set of entries
const plan = planHoists(entries, owners)Parameters
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
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<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
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
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
kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"— Properties
fingerprint:string— $N suffixes stripped from identifier nodes; the raw text is still the chunk-body source.Properties
kind:"default" | "named" | "namespace" | "cjs-namespace" | "cjs-named"— Properties
ownerOf:Map<string, string>— $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.◆ 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 = string