From 59b7e4bc5c4819aa058213b0ff71d479dba3b08c Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 3 Dec 2022 06:40:07 +0100 Subject: [PATCH 1/5] Slightly simplify graph analysis Also generates dynamicImportsByEntry that will help us to track already loaded modules more efficiently. --- src/utils/chunkAssignment.ts | 57 +++++++++++++++--------------------- test/hooks/index.js | 2 ++ 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index 945a7393b7f..aec377c3bbf 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -27,9 +27,8 @@ export function getChunkAssignments( } const assignedEntryPointsByModule: DependentModuleMap = new Map(); - const { dependentEntryPointsByModule, dynamicEntryModules } = analyzeModuleGraph(entryModules); - const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = - getDynamicDependentEntryPoints(dependentEntryPointsByModule, dynamicEntryModules); + const { dependentEntryPointsByModule, dynamicallyDependentEntryPointsByDynamicEntry } = + analyzeModuleGraph(entryModules); const staticEntries = new Set(entryModules); function assignEntryToStaticDependencies( @@ -58,6 +57,7 @@ export function getChunkAssignments( } } + // TODO Lukas this is slow function areEntryPointsContainedOrDynamicallyDependent( entryPoints: ReadonlySet, containedIn: ReadonlySet @@ -82,7 +82,7 @@ export function getChunkAssignments( } } - for (const entry of dynamicEntryModules) { + for (const entry of dynamicallyDependentEntryPointsByDynamicEntry.keys()) { if (!modulesInManualChunks.has(entry)) { assignEntryToStaticDependencies( entry, @@ -93,7 +93,7 @@ export function getChunkAssignments( chunkDefinitions.push( ...createChunks( - [...entryModules, ...dynamicEntryModules], + [...entryModules, ...dynamicallyDependentEntryPointsByDynamicEntry.keys()], assignedEntryPointsByModule, minChunkSize ) @@ -120,10 +120,12 @@ function addStaticDependenciesToManualChunk( function analyzeModuleGraph(entryModules: readonly Module[]): { dependentEntryPointsByModule: DependentModuleMap; - dynamicEntryModules: Set; + dynamicImportsByEntry: DependentModuleMap; + dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap; } { - const dynamicEntryModules = new Set(); const dependentEntryPointsByModule: DependentModuleMap = new Map(); + const dynamicImportsByEntry: DependentModuleMap = new Map(); + const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map(); const entriesToHandle = new Set(entryModules); for (const currentEntry of entriesToHandle) { const modulesToHandle = new Set([currentEntry]); @@ -136,40 +138,29 @@ function analyzeModuleGraph(entryModules: readonly Module[]): { } for (const { resolution } of module.dynamicImports) { if (resolution instanceof Module && resolution.includedDynamicImporters.length > 0) { - dynamicEntryModules.add(resolution); + getOrCreate(dynamicImportsByEntry, currentEntry, () => new Set()).add(resolution); + getOrCreate( + dynamicallyDependentEntryPointsByDynamicEntry, + resolution, + () => new Set() + ).add(currentEntry); entriesToHandle.add(resolution); } } for (const dependency of module.implicitlyLoadedBefore) { - dynamicEntryModules.add(dependency); + getOrCreate(dynamicImportsByEntry, currentEntry, () => new Set()).add(dependency); + getOrCreate(dynamicallyDependentEntryPointsByDynamicEntry, dependency, () => new Set()).add( + currentEntry + ); entriesToHandle.add(dependency); } } } - return { dependentEntryPointsByModule, dynamicEntryModules }; -} - -function getDynamicDependentEntryPoints( - dependentEntryPointsByModule: DependentModuleMap, - dynamicEntryModules: ReadonlySet -): DependentModuleMap { - const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map(); - for (const dynamicEntry of dynamicEntryModules) { - const dynamicDependentEntryPoints = getOrCreate( - dynamicallyDependentEntryPointsByDynamicEntry, - dynamicEntry, - () => new Set() - ); - for (const importer of [ - ...dynamicEntry.includedDynamicImporters, - ...dynamicEntry.implicitlyLoadedAfter - ]) { - for (const entryPoint of dependentEntryPointsByModule.get(importer)!) { - dynamicDependentEntryPoints.add(entryPoint); - } - } - } - return dynamicallyDependentEntryPointsByDynamicEntry; + return { + dependentEntryPointsByModule, + dynamicallyDependentEntryPointsByDynamicEntry, + dynamicImportsByEntry + }; } interface ChunkDescription { diff --git a/test/hooks/index.js b/test/hooks/index.js index a5d52d0cdb5..f4a9aa71c7c 100644 --- a/test/hooks/index.js +++ b/test/hooks/index.js @@ -7,6 +7,8 @@ const { loader, wait } = require('../utils.js'); const TEMP_DIR = path.join(__dirname, 'tmp'); describe('hooks', () => { + before(() => remove(TEMP_DIR)); + it('allows to replace file with dir in the outputOptions hook', async () => { const bundle = await rollup.rollup({ input: 'input', From 405c3645a811dc6ecdbdc256322c375068f320e1 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 4 Dec 2022 07:30:04 +0100 Subject: [PATCH 2/5] Implement new algorithm with hopefully better performance --- src/utils/chunkAssignment.ts | 98 +++++++++++++++++++++++------------- src/utils/getOrCreate.ts | 2 +- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index aec377c3bbf..98ec63ac289 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -27,24 +27,29 @@ export function getChunkAssignments( } const assignedEntryPointsByModule: DependentModuleMap = new Map(); - const { dependentEntryPointsByModule, dynamicallyDependentEntryPointsByDynamicEntry } = - analyzeModuleGraph(entryModules); - const staticEntries = new Set(entryModules); + const { + allModules, + dependentEntryPointsByModule, + dynamicallyDependentEntryPointsByDynamicEntry, + dynamicImportsByEntry + } = analyzeModuleGraph(entryModules); + const alreadyLoadedModulesByDynamicEntry = getAlreadyLoadedModulesByDynamicEntry( + allModules, + dependentEntryPointsByModule, + dynamicImportsByEntry, + dynamicallyDependentEntryPointsByDynamicEntry + ); function assignEntryToStaticDependencies( entry: Module, - dynamicDependentEntryPoints: ReadonlySet | null + alreadyLoadedModules: ReadonlySet | null ) { const modulesToHandle = new Set([entry]); for (const module of modulesToHandle) { const assignedEntryPoints = getOrCreate(assignedEntryPointsByModule, module, () => new Set()); - if ( - dynamicDependentEntryPoints && - areEntryPointsContainedOrDynamicallyDependent( - dynamicDependentEntryPoints, - dependentEntryPointsByModule.get(module)! - ) - ) { + // If the module is "already loaded" for this dynamic entry, we do not need + // to mark it for this dynamic entry + if (alreadyLoadedModules?.has(module)) { continue; } else { assignedEntryPoints.add(entry); @@ -57,25 +62,6 @@ export function getChunkAssignments( } } - // TODO Lukas this is slow - function areEntryPointsContainedOrDynamicallyDependent( - entryPoints: ReadonlySet, - containedIn: ReadonlySet - ): boolean { - const entriesToCheck = new Set(entryPoints); - for (const entry of entriesToCheck) { - if (!containedIn.has(entry)) { - if (staticEntries.has(entry)) return false; - const dynamicallyDependentEntryPoints = - dynamicallyDependentEntryPointsByDynamicEntry.get(entry)!; - for (const dependentEntry of dynamicallyDependentEntryPoints) { - entriesToCheck.add(dependentEntry); - } - } - } - return true; - } - for (const entry of entryModules) { if (!modulesInManualChunks.has(entry)) { assignEntryToStaticDependencies(entry, null); @@ -84,10 +70,7 @@ export function getChunkAssignments( for (const entry of dynamicallyDependentEntryPointsByDynamicEntry.keys()) { if (!modulesInManualChunks.has(entry)) { - assignEntryToStaticDependencies( - entry, - dynamicallyDependentEntryPointsByDynamicEntry.get(entry)! - ); + assignEntryToStaticDependencies(entry, alreadyLoadedModulesByDynamicEntry.get(entry) || null); } } @@ -119,50 +102,93 @@ function addStaticDependenciesToManualChunk( } function analyzeModuleGraph(entryModules: readonly Module[]): { + allModules: Set; dependentEntryPointsByModule: DependentModuleMap; dynamicImportsByEntry: DependentModuleMap; dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap; } { + const allModules = new Set(entryModules); const dependentEntryPointsByModule: DependentModuleMap = new Map(); const dynamicImportsByEntry: DependentModuleMap = new Map(); const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map(); const entriesToHandle = new Set(entryModules); for (const currentEntry of entriesToHandle) { const modulesToHandle = new Set([currentEntry]); + const dynamicImports = new Set(); + dynamicImportsByEntry.set(currentEntry, dynamicImports); for (const module of modulesToHandle) { getOrCreate(dependentEntryPointsByModule, module, () => new Set()).add(currentEntry); for (const dependency of module.getDependenciesToBeIncluded()) { if (!(dependency instanceof ExternalModule)) { modulesToHandle.add(dependency); + allModules.add(dependency); } } for (const { resolution } of module.dynamicImports) { if (resolution instanceof Module && resolution.includedDynamicImporters.length > 0) { - getOrCreate(dynamicImportsByEntry, currentEntry, () => new Set()).add(resolution); + dynamicImports.add(resolution); getOrCreate( dynamicallyDependentEntryPointsByDynamicEntry, resolution, () => new Set() ).add(currentEntry); entriesToHandle.add(resolution); + allModules.add(resolution); } } for (const dependency of module.implicitlyLoadedBefore) { - getOrCreate(dynamicImportsByEntry, currentEntry, () => new Set()).add(dependency); + dynamicImports.add(dependency); getOrCreate(dynamicallyDependentEntryPointsByDynamicEntry, dependency, () => new Set()).add( currentEntry ); entriesToHandle.add(dependency); + allModules.add(dependency); } } } return { + allModules, dependentEntryPointsByModule, dynamicallyDependentEntryPointsByDynamicEntry, dynamicImportsByEntry }; } +function getAlreadyLoadedModulesByDynamicEntry( + allModules: Set, + dependentEntryPointsByModule: DependentModuleMap, + dynamicImportsByEntry: DependentModuleMap, + dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap +): DependentModuleMap { + const alreadyLoadedModulesByEntry: DependentModuleMap = new Map(); + for (const module of allModules) { + const dependentEntryPoints = dependentEntryPointsByModule.get(module)!; + for (const entry of dependentEntryPoints) { + const dynamicEntriesToHandle = [...dynamicImportsByEntry.get(entry)!]; + nextDynamicEntry: for (const dynamicEntry of dynamicEntriesToHandle) { + if (alreadyLoadedModulesByEntry.get(dynamicEntry)?.has(module)) { + continue; + } + for (const siblingDependentEntry of dynamicallyDependentEntryPointsByDynamicEntry.get( + dynamicEntry + )!) { + if ( + !( + dependentEntryPoints.has(siblingDependentEntry) || + alreadyLoadedModulesByEntry.get(siblingDependentEntry)?.has(module) + ) + ) { + continue nextDynamicEntry; + } + } + getOrCreate(alreadyLoadedModulesByEntry, dynamicEntry, () => new Set()).add(module); + dynamicEntriesToHandle.push(...dynamicImportsByEntry.get(dynamicEntry)!); + } + } + } + return alreadyLoadedModulesByEntry; +} + interface ChunkDescription { alias: null; modules: Module[]; diff --git a/src/utils/getOrCreate.ts b/src/utils/getOrCreate.ts index 8fbf119f4fb..e1f6157f3c1 100644 --- a/src/utils/getOrCreate.ts +++ b/src/utils/getOrCreate.ts @@ -1,6 +1,6 @@ export function getOrCreate(map: Map, key: K, init: () => V): V { const existing = map.get(key); - if (existing) { + if (existing !== undefined) { return existing; } const value = init(); From 20d422a90e9d2c9a9029a1bedef22488f57d5e90 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 5 Dec 2022 06:30:49 +0100 Subject: [PATCH 3/5] Further refine chunking algorithm --- src/utils/chunkAssignment.ts | 115 ++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index 98ec63ac289..74097de0553 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -8,7 +8,7 @@ type DependentModuleMap = Map>; type ChunkDefinitions = { alias: string | null; modules: Module[] }[]; export function getChunkAssignments( - entryModules: readonly Module[], + entries: readonly Module[], manualChunkAliasByEntry: ReadonlyMap, minChunkSize: number ): ChunkDefinitions { @@ -25,58 +25,34 @@ export function getChunkAssignments( for (const [alias, modules] of Object.entries(manualChunkModulesByAlias)) { chunkDefinitions.push({ alias, modules }); } - + const alreadyLoadedModulesByDynamicEntry = getAlreadyLoadedModulesByDynamicEntry(entries); const assignedEntryPointsByModule: DependentModuleMap = new Map(); - const { - allModules, - dependentEntryPointsByModule, - dynamicallyDependentEntryPointsByDynamicEntry, - dynamicImportsByEntry - } = analyzeModuleGraph(entryModules); - const alreadyLoadedModulesByDynamicEntry = getAlreadyLoadedModulesByDynamicEntry( - allModules, - dependentEntryPointsByModule, - dynamicImportsByEntry, - dynamicallyDependentEntryPointsByDynamicEntry - ); - - function assignEntryToStaticDependencies( - entry: Module, - alreadyLoadedModules: ReadonlySet | null - ) { - const modulesToHandle = new Set([entry]); - for (const module of modulesToHandle) { - const assignedEntryPoints = getOrCreate(assignedEntryPointsByModule, module, () => new Set()); - // If the module is "already loaded" for this dynamic entry, we do not need - // to mark it for this dynamic entry - if (alreadyLoadedModules?.has(module)) { - continue; - } else { - assignedEntryPoints.add(entry); - } - for (const dependency of module.getDependenciesToBeIncluded()) { - if (!(dependency instanceof ExternalModule || modulesInManualChunks.has(dependency))) { - modulesToHandle.add(dependency); - } - } - } - } - for (const entry of entryModules) { + for (const entry of entries) { if (!modulesInManualChunks.has(entry)) { - assignEntryToStaticDependencies(entry, null); + assignEntryToStaticDependencies( + entry, + undefined, + assignedEntryPointsByModule, + modulesInManualChunks + ); } } - for (const entry of dynamicallyDependentEntryPointsByDynamicEntry.keys()) { + for (const entry of alreadyLoadedModulesByDynamicEntry.keys()) { if (!modulesInManualChunks.has(entry)) { - assignEntryToStaticDependencies(entry, alreadyLoadedModulesByDynamicEntry.get(entry) || null); + assignEntryToStaticDependencies( + entry, + alreadyLoadedModulesByDynamicEntry.get(entry), + assignedEntryPointsByModule, + modulesInManualChunks + ); } } chunkDefinitions.push( ...createChunks( - [...entryModules, ...dynamicallyDependentEntryPointsByDynamicEntry.keys()], + [...entries, ...alreadyLoadedModulesByDynamicEntry.keys()], assignedEntryPointsByModule, minChunkSize ) @@ -101,12 +77,9 @@ function addStaticDependenciesToManualChunk( } } -function analyzeModuleGraph(entryModules: readonly Module[]): { - allModules: Set; - dependentEntryPointsByModule: DependentModuleMap; - dynamicImportsByEntry: DependentModuleMap; - dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap; -} { +function getAlreadyLoadedModulesByDynamicEntry( + entryModules: readonly Module[] +): DependentModuleMap { const allModules = new Set(entryModules); const dependentEntryPointsByModule: DependentModuleMap = new Map(); const dynamicImportsByEntry: DependentModuleMap = new Map(); @@ -146,27 +119,31 @@ function analyzeModuleGraph(entryModules: readonly Module[]): { } } } - return { + return buildAlreadyLoadedModulesByDynamicEntry( allModules, dependentEntryPointsByModule, - dynamicallyDependentEntryPointsByDynamicEntry, - dynamicImportsByEntry - }; + dynamicImportsByEntry, + dynamicallyDependentEntryPointsByDynamicEntry + ); } -function getAlreadyLoadedModulesByDynamicEntry( +function buildAlreadyLoadedModulesByDynamicEntry( allModules: Set, dependentEntryPointsByModule: DependentModuleMap, dynamicImportsByEntry: DependentModuleMap, dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap ): DependentModuleMap { - const alreadyLoadedModulesByEntry: DependentModuleMap = new Map(); + const alreadyLoadedModulesByDynamicEntry: DependentModuleMap = new Map(); + for (const dynamicEntry of dynamicallyDependentEntryPointsByDynamicEntry.keys()) { + alreadyLoadedModulesByDynamicEntry.set(dynamicEntry, new Set()); + } for (const module of allModules) { const dependentEntryPoints = dependentEntryPointsByModule.get(module)!; for (const entry of dependentEntryPoints) { const dynamicEntriesToHandle = [...dynamicImportsByEntry.get(entry)!]; nextDynamicEntry: for (const dynamicEntry of dynamicEntriesToHandle) { - if (alreadyLoadedModulesByEntry.get(dynamicEntry)?.has(module)) { + const alreadyLoadedModules = alreadyLoadedModulesByDynamicEntry.get(dynamicEntry)!; + if (alreadyLoadedModules.has(module)) { continue; } for (const siblingDependentEntry of dynamicallyDependentEntryPointsByDynamicEntry.get( @@ -175,18 +152,42 @@ function getAlreadyLoadedModulesByDynamicEntry( if ( !( dependentEntryPoints.has(siblingDependentEntry) || - alreadyLoadedModulesByEntry.get(siblingDependentEntry)?.has(module) + alreadyLoadedModulesByDynamicEntry.get(siblingDependentEntry)?.has(module) ) ) { continue nextDynamicEntry; } } - getOrCreate(alreadyLoadedModulesByEntry, dynamicEntry, () => new Set()).add(module); + alreadyLoadedModules.add(module); dynamicEntriesToHandle.push(...dynamicImportsByEntry.get(dynamicEntry)!); } } } - return alreadyLoadedModulesByEntry; + return alreadyLoadedModulesByDynamicEntry; +} + +function assignEntryToStaticDependencies( + entry: Module, + alreadyLoadedModules: ReadonlySet | undefined, + assignedEntryPointsByModule: DependentModuleMap, + modulesInManualChunks: Set +) { + const modulesToHandle = new Set([entry]); + for (const module of modulesToHandle) { + const assignedEntryPoints = getOrCreate(assignedEntryPointsByModule, module, () => new Set()); + // If the module is "already loaded" for this dynamic entry, we do not need + // to mark it for this dynamic entry + if (alreadyLoadedModules?.has(module)) { + continue; + } else { + assignedEntryPoints.add(entry); + } + for (const dependency of module.getDependenciesToBeIncluded()) { + if (!(dependency instanceof ExternalModule || modulesInManualChunks.has(dependency))) { + modulesToHandle.add(dependency); + } + } + } } interface ChunkDescription { From dc0f1009626797fa7d176df32f25fbe482ce51ce Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 7 Dec 2022 15:54:30 +0100 Subject: [PATCH 4/5] Add cut-off condition to only back-track modules if there are few imports --- src/utils/chunkAssignment.ts | 226 +++++++++--------- .../cut-off/_config.js | 7 + .../cut-off/_expected/amd/generated-dep.js | 7 + .../_expected/amd/generated-dynamic1.js | 6 + .../_expected/amd/generated-dynamic2.js | 5 + .../cut-off/_expected/amd/main1.js | 6 + .../cut-off/_expected/amd/main2.js | 6 + .../cut-off/_expected/amd/main3.js | 6 + .../cut-off/_expected/amd/main4.js | 6 + .../cut-off/_expected/cjs/generated-dep.js | 5 + .../_expected/cjs/generated-dynamic1.js | 6 + .../_expected/cjs/generated-dynamic2.js | 5 + .../cut-off/_expected/cjs/main1.js | 6 + .../cut-off/_expected/cjs/main2.js | 6 + .../cut-off/_expected/cjs/main3.js | 6 + .../cut-off/_expected/cjs/main4.js | 6 + .../cut-off/_expected/es/generated-dep.js | 3 + .../_expected/es/generated-dynamic1.js | 4 + .../_expected/es/generated-dynamic2.js | 3 + .../cut-off/_expected/es/main1.js | 4 + .../cut-off/_expected/es/main2.js | 4 + .../cut-off/_expected/es/main3.js | 4 + .../cut-off/_expected/es/main4.js | 4 + .../cut-off/_expected/system/generated-dep.js | 10 + .../_expected/system/generated-dynamic1.js | 15 ++ .../_expected/system/generated-dynamic2.js | 14 ++ .../cut-off/_expected/system/main1.js | 15 ++ .../cut-off/_expected/system/main2.js | 15 ++ .../cut-off/_expected/system/main3.js | 15 ++ .../cut-off/_expected/system/main4.js | 15 ++ .../improved-dynamic-chunks/cut-off/dep.js | 1 + .../cut-off/dynamic1.js | 4 + .../cut-off/dynamic2.js | 2 + .../improved-dynamic-chunks/cut-off/main1.js | 3 + .../improved-dynamic-chunks/cut-off/main2.js | 3 + .../improved-dynamic-chunks/cut-off/main3.js | 3 + .../improved-dynamic-chunks/cut-off/main4.js | 3 + 37 files changed, 350 insertions(+), 109 deletions(-) create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_config.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dep.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main3.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main4.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dep.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main3.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main4.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dep.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main3.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main4.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dep.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main3.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main4.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/dep.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/main1.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/main2.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/main3.js create mode 100644 test/chunking-form/samples/improved-dynamic-chunks/cut-off/main4.js diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index 74097de0553..ad3ab13474e 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -5,6 +5,7 @@ import { concatLazy } from './iterators'; import { timeEnd, timeStart } from './timers'; type DependentModuleMap = Map>; +type ReadonlyDependentModuleMap = ReadonlyMap>; type ChunkDefinitions = { alias: string | null; modules: Module[] }[]; export function getChunkAssignments( @@ -25,38 +26,27 @@ export function getChunkAssignments( for (const [alias, modules] of Object.entries(manualChunkModulesByAlias)) { chunkDefinitions.push({ alias, modules }); } - const alreadyLoadedModulesByDynamicEntry = getAlreadyLoadedModulesByDynamicEntry(entries); - const assignedEntryPointsByModule: DependentModuleMap = new Map(); - for (const entry of entries) { - if (!modulesInManualChunks.has(entry)) { - assignEntryToStaticDependencies( - entry, - undefined, - assignedEntryPointsByModule, - modulesInManualChunks - ); - } - } + const { allEntries, dependentEntriesByModule, dynamicallyDependentEntriesByDynamicEntry } = + analyzeModuleGraph(entries); - for (const entry of alreadyLoadedModulesByDynamicEntry.keys()) { + const staticEntries = new Set(entries); + const assignedEntriesByModule: DependentModuleMap = new Map(); + + for (const entry of allEntries) { if (!modulesInManualChunks.has(entry)) { assignEntryToStaticDependencies( entry, - alreadyLoadedModulesByDynamicEntry.get(entry), - assignedEntryPointsByModule, - modulesInManualChunks + dependentEntriesByModule, + assignedEntriesByModule, + modulesInManualChunks, + staticEntries, + dynamicallyDependentEntriesByDynamicEntry ); } } - chunkDefinitions.push( - ...createChunks( - [...entries, ...alreadyLoadedModulesByDynamicEntry.keys()], - assignedEntryPointsByModule, - minChunkSize - ) - ); + chunkDefinitions.push(...createChunks(allEntries, assignedEntriesByModule, minChunkSize)); return chunkDefinitions; } @@ -77,110 +67,98 @@ function addStaticDependenciesToManualChunk( } } -function getAlreadyLoadedModulesByDynamicEntry( - entryModules: readonly Module[] -): DependentModuleMap { - const allModules = new Set(entryModules); - const dependentEntryPointsByModule: DependentModuleMap = new Map(); - const dynamicImportsByEntry: DependentModuleMap = new Map(); - const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map(); - const entriesToHandle = new Set(entryModules); - for (const currentEntry of entriesToHandle) { +function analyzeModuleGraph(entries: Iterable): { + allEntries: Iterable; + dependentEntriesByModule: DependentModuleMap; + dynamicallyDependentEntriesByDynamicEntry: DependentModuleMap; +} { + const dynamicEntries = new Set(); + const dependentEntriesByModule: DependentModuleMap = new Map(); + const allEntries = new Set(entries); + for (const currentEntry of allEntries) { const modulesToHandle = new Set([currentEntry]); - const dynamicImports = new Set(); - dynamicImportsByEntry.set(currentEntry, dynamicImports); for (const module of modulesToHandle) { - getOrCreate(dependentEntryPointsByModule, module, () => new Set()).add(currentEntry); + getOrCreate(dependentEntriesByModule, module, () => new Set()).add(currentEntry); for (const dependency of module.getDependenciesToBeIncluded()) { if (!(dependency instanceof ExternalModule)) { modulesToHandle.add(dependency); - allModules.add(dependency); } } for (const { resolution } of module.dynamicImports) { - if (resolution instanceof Module && resolution.includedDynamicImporters.length > 0) { - dynamicImports.add(resolution); - getOrCreate( - dynamicallyDependentEntryPointsByDynamicEntry, - resolution, - () => new Set() - ).add(currentEntry); - entriesToHandle.add(resolution); - allModules.add(resolution); + if ( + resolution instanceof Module && + resolution.includedDynamicImporters.length > 0 && + !allEntries.has(resolution) + ) { + dynamicEntries.add(resolution); + allEntries.add(resolution); } } for (const dependency of module.implicitlyLoadedBefore) { - dynamicImports.add(dependency); - getOrCreate(dynamicallyDependentEntryPointsByDynamicEntry, dependency, () => new Set()).add( - currentEntry - ); - entriesToHandle.add(dependency); - allModules.add(dependency); + if (!allEntries.has(dependency)) { + dynamicEntries.add(dependency); + allEntries.add(dependency); + } } } } - return buildAlreadyLoadedModulesByDynamicEntry( - allModules, - dependentEntryPointsByModule, - dynamicImportsByEntry, - dynamicallyDependentEntryPointsByDynamicEntry - ); + return { + allEntries, + dependentEntriesByModule, + dynamicallyDependentEntriesByDynamicEntry: getDynamicallyDependentEntriesByDynamicEntry( + dependentEntriesByModule, + dynamicEntries + ) + }; } -function buildAlreadyLoadedModulesByDynamicEntry( - allModules: Set, - dependentEntryPointsByModule: DependentModuleMap, - dynamicImportsByEntry: DependentModuleMap, - dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap +function getDynamicallyDependentEntriesByDynamicEntry( + dependentEntriesByModule: ReadonlyDependentModuleMap, + dynamicEntries: ReadonlySet ): DependentModuleMap { - const alreadyLoadedModulesByDynamicEntry: DependentModuleMap = new Map(); - for (const dynamicEntry of dynamicallyDependentEntryPointsByDynamicEntry.keys()) { - alreadyLoadedModulesByDynamicEntry.set(dynamicEntry, new Set()); - } - for (const module of allModules) { - const dependentEntryPoints = dependentEntryPointsByModule.get(module)!; - for (const entry of dependentEntryPoints) { - const dynamicEntriesToHandle = [...dynamicImportsByEntry.get(entry)!]; - nextDynamicEntry: for (const dynamicEntry of dynamicEntriesToHandle) { - const alreadyLoadedModules = alreadyLoadedModulesByDynamicEntry.get(dynamicEntry)!; - if (alreadyLoadedModules.has(module)) { - continue; - } - for (const siblingDependentEntry of dynamicallyDependentEntryPointsByDynamicEntry.get( - dynamicEntry - )!) { - if ( - !( - dependentEntryPoints.has(siblingDependentEntry) || - alreadyLoadedModulesByDynamicEntry.get(siblingDependentEntry)?.has(module) - ) - ) { - continue nextDynamicEntry; - } - } - alreadyLoadedModules.add(module); - dynamicEntriesToHandle.push(...dynamicImportsByEntry.get(dynamicEntry)!); + const dynamicallyDependentEntriesByDynamicEntry: DependentModuleMap = new Map(); + for (const dynamicEntry of dynamicEntries) { + const dynamicallyDependentEntries = getOrCreate( + dynamicallyDependentEntriesByDynamicEntry, + dynamicEntry, + () => new Set() + ); + for (const importer of [ + ...dynamicEntry.includedDynamicImporters, + ...dynamicEntry.implicitlyLoadedAfter + ]) { + for (const entry of dependentEntriesByModule.get(importer)!) { + dynamicallyDependentEntries.add(entry); } } } - return alreadyLoadedModulesByDynamicEntry; + return dynamicallyDependentEntriesByDynamicEntry; } function assignEntryToStaticDependencies( entry: Module, - alreadyLoadedModules: ReadonlySet | undefined, - assignedEntryPointsByModule: DependentModuleMap, - modulesInManualChunks: Set + dependentEntriesByModule: ReadonlyDependentModuleMap, + assignedEntriesByModule: DependentModuleMap, + modulesInManualChunks: ReadonlySet, + staticEntries: ReadonlySet, + dynamicallyDependentEntriesByDynamicEntry: ReadonlyDependentModuleMap ) { + const dynamicallyDependentEntries = dynamicallyDependentEntriesByDynamicEntry.get(entry); const modulesToHandle = new Set([entry]); for (const module of modulesToHandle) { - const assignedEntryPoints = getOrCreate(assignedEntryPointsByModule, module, () => new Set()); - // If the module is "already loaded" for this dynamic entry, we do not need - // to mark it for this dynamic entry - if (alreadyLoadedModules?.has(module)) { + const assignedEntries = getOrCreate(assignedEntriesByModule, module, () => new Set()); + if ( + dynamicallyDependentEntries && + isModuleAlreadyLoaded( + dynamicallyDependentEntries, + dependentEntriesByModule.get(module)!, + staticEntries, + dynamicallyDependentEntriesByDynamicEntry + ) + ) { continue; } else { - assignedEntryPoints.add(entry); + assignedEntries.add(entry); } for (const dependency of module.getDependenciesToBeIncluded()) { if (!(dependency instanceof ExternalModule || modulesInManualChunks.has(dependency))) { @@ -190,6 +168,39 @@ function assignEntryToStaticDependencies( } } +const MAX_ENTRIES_TO_CHECK_FOR_SHARED_DEPENDENCIES = 3; + +// An approach to further speed this up might be +// - first, create chunks without looking for modules already in memory +// - all modules that are in the same chunk after this will behave the same +// -> Do not iterate by module but by equivalence group and merge chunks +function isModuleAlreadyLoaded( + dynamicallyDependentEntries: ReadonlySet, + containedIn: ReadonlySet, + staticEntries: ReadonlySet, + dynamicallyDependentEntriesByDynamicEntry: ReadonlyDependentModuleMap +): boolean { + if (dynamicallyDependentEntries.size > MAX_ENTRIES_TO_CHECK_FOR_SHARED_DEPENDENCIES) { + return false; + } + const entriesToCheck = new Set(dynamicallyDependentEntries); + for (const entry of entriesToCheck) { + if (!containedIn.has(entry)) { + if (staticEntries.has(entry)) { + return false; + } + const dynamicallyDependentEntries = dynamicallyDependentEntriesByDynamicEntry.get(entry)!; + if (dynamicallyDependentEntries.size > MAX_ENTRIES_TO_CHECK_FOR_SHARED_DEPENDENCIES) { + return false; + } + for (const dependentEntry of dynamicallyDependentEntries) { + entriesToCheck.add(dependentEntry); + } + } + } + return true; +} + interface ChunkDescription { alias: null; modules: Module[]; @@ -202,14 +213,11 @@ interface MergeableChunkDescription extends ChunkDescription { } function createChunks( - allEntryPoints: readonly Module[], - assignedEntryPointsByModule: DependentModuleMap, + allEntries: Iterable, + assignedEntriesByModule: DependentModuleMap, minChunkSize: number ): ChunkDefinitions { - const chunkModulesBySignature = getChunkModulesBySignature( - assignedEntryPointsByModule, - allEntryPoints - ); + const chunkModulesBySignature = getChunkModulesBySignature(assignedEntriesByModule, allEntries); return minChunkSize === 0 ? Object.values(chunkModulesBySignature).map(modules => ({ alias: null, @@ -269,14 +277,14 @@ const CHAR_INDEPENDENT = '_'; const CHAR_CODE_DEPENDENT = CHAR_DEPENDENT.charCodeAt(0); function getChunkModulesBySignature( - assignedEntryPointsByModule: Map>, - allEntryPoints: readonly Module[] + assignedEntriesByModule: ReadonlyDependentModuleMap, + allEntries: Iterable ) { const chunkModules: { [chunkSignature: string]: Module[] } = Object.create(null); - for (const [module, assignedEntryPoints] of assignedEntryPointsByModule) { + for (const [module, assignedEntries] of assignedEntriesByModule) { let chunkSignature = ''; - for (const entry of allEntryPoints) { - chunkSignature += assignedEntryPoints.has(entry) ? CHAR_DEPENDENT : CHAR_INDEPENDENT; + for (const entry of allEntries) { + chunkSignature += assignedEntries.has(entry) ? CHAR_DEPENDENT : CHAR_INDEPENDENT; } const chunk = chunkModules[chunkSignature]; if (chunk) { diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_config.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_config.js new file mode 100644 index 00000000000..c576b1c3250 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_config.js @@ -0,0 +1,7 @@ +module.exports = { + description: + 'does not avoid separate chunks if too many modules dynamically import the same chunk', + options: { + input: ['main1', 'main2', 'main3', 'main4'] + } +}; diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dep.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dep.js new file mode 100644 index 00000000000..2338648d763 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dep.js @@ -0,0 +1,7 @@ +define(['exports'], (function (exports) { 'use strict'; + + const value = 'shared'; + + exports.value = value; + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic1.js new file mode 100644 index 00000000000..0521001fece --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic1.js @@ -0,0 +1,6 @@ +define(['require', './generated-dep'], (function (require, dep) { 'use strict'; + + console.log('dynamic1', dep.value); + new Promise(function (resolve, reject) { require(['./generated-dynamic2'], resolve, reject); }); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic2.js new file mode 100644 index 00000000000..7e1d9ca3c22 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/generated-dynamic2.js @@ -0,0 +1,5 @@ +define(['./generated-dep'], (function (dep) { 'use strict'; + + console.log('dynamic2', dep.value); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main1.js new file mode 100644 index 00000000000..eed65cfcb79 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main1.js @@ -0,0 +1,6 @@ +define(['require', './generated-dep'], (function (require, dep) { 'use strict'; + + console.log('main1', dep.value); + new Promise(function (resolve, reject) { require(['./generated-dynamic1'], resolve, reject); }); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main2.js new file mode 100644 index 00000000000..eed65cfcb79 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main2.js @@ -0,0 +1,6 @@ +define(['require', './generated-dep'], (function (require, dep) { 'use strict'; + + console.log('main1', dep.value); + new Promise(function (resolve, reject) { require(['./generated-dynamic1'], resolve, reject); }); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main3.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main3.js new file mode 100644 index 00000000000..eed65cfcb79 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main3.js @@ -0,0 +1,6 @@ +define(['require', './generated-dep'], (function (require, dep) { 'use strict'; + + console.log('main1', dep.value); + new Promise(function (resolve, reject) { require(['./generated-dynamic1'], resolve, reject); }); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main4.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main4.js new file mode 100644 index 00000000000..eed65cfcb79 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/amd/main4.js @@ -0,0 +1,6 @@ +define(['require', './generated-dep'], (function (require, dep) { 'use strict'; + + console.log('main1', dep.value); + new Promise(function (resolve, reject) { require(['./generated-dynamic1'], resolve, reject); }); + +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dep.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dep.js new file mode 100644 index 00000000000..31a412b6dc4 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dep.js @@ -0,0 +1,5 @@ +'use strict'; + +const value = 'shared'; + +exports.value = value; diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic1.js new file mode 100644 index 00000000000..bc0ad9f1c2a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic1.js @@ -0,0 +1,6 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('dynamic1', dep.value); +Promise.resolve().then(function () { return require('./generated-dynamic2.js'); }); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic2.js new file mode 100644 index 00000000000..af07a8e556a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/generated-dynamic2.js @@ -0,0 +1,5 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('dynamic2', dep.value); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main1.js new file mode 100644 index 00000000000..5d139ba4b8d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main1.js @@ -0,0 +1,6 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main1', dep.value); +Promise.resolve().then(function () { return require('./generated-dynamic1.js'); }); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main2.js new file mode 100644 index 00000000000..5d139ba4b8d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main2.js @@ -0,0 +1,6 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main1', dep.value); +Promise.resolve().then(function () { return require('./generated-dynamic1.js'); }); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main3.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main3.js new file mode 100644 index 00000000000..5d139ba4b8d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main3.js @@ -0,0 +1,6 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main1', dep.value); +Promise.resolve().then(function () { return require('./generated-dynamic1.js'); }); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main4.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main4.js new file mode 100644 index 00000000000..5d139ba4b8d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/cjs/main4.js @@ -0,0 +1,6 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main1', dep.value); +Promise.resolve().then(function () { return require('./generated-dynamic1.js'); }); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dep.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dep.js new file mode 100644 index 00000000000..27b7b7de159 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dep.js @@ -0,0 +1,3 @@ +const value = 'shared'; + +export { value as v }; diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic1.js new file mode 100644 index 00000000000..67428789ad0 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic1.js @@ -0,0 +1,4 @@ +import { v as value } from './generated-dep.js'; + +console.log('dynamic1', value); +import('./generated-dynamic2.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic2.js new file mode 100644 index 00000000000..0c2d81a045a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/generated-dynamic2.js @@ -0,0 +1,3 @@ +import { v as value } from './generated-dep.js'; + +console.log('dynamic2', value); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main1.js new file mode 100644 index 00000000000..40e11af6e3d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main1.js @@ -0,0 +1,4 @@ +import { v as value } from './generated-dep.js'; + +console.log('main1', value); +import('./generated-dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main2.js new file mode 100644 index 00000000000..40e11af6e3d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main2.js @@ -0,0 +1,4 @@ +import { v as value } from './generated-dep.js'; + +console.log('main1', value); +import('./generated-dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main3.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main3.js new file mode 100644 index 00000000000..40e11af6e3d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main3.js @@ -0,0 +1,4 @@ +import { v as value } from './generated-dep.js'; + +console.log('main1', value); +import('./generated-dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main4.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main4.js new file mode 100644 index 00000000000..40e11af6e3d --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/es/main4.js @@ -0,0 +1,4 @@ +import { v as value } from './generated-dep.js'; + +console.log('main1', value); +import('./generated-dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dep.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dep.js new file mode 100644 index 00000000000..a8d6d67aa46 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dep.js @@ -0,0 +1,10 @@ +System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + const value = exports('v', 'shared'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic1.js new file mode 100644 index 00000000000..57cef271845 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic1.js @@ -0,0 +1,15 @@ +System.register(['./generated-dep.js'], (function (exports, module) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('dynamic1', value); + module.import('./generated-dynamic2.js'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic2.js new file mode 100644 index 00000000000..ba93f808068 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/generated-dynamic2.js @@ -0,0 +1,14 @@ +System.register(['./generated-dep.js'], (function () { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('dynamic2', value); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main1.js new file mode 100644 index 00000000000..a469eaf5724 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main1.js @@ -0,0 +1,15 @@ +System.register(['./generated-dep.js'], (function (exports, module) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('main1', value); + module.import('./generated-dynamic1.js'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main2.js new file mode 100644 index 00000000000..a469eaf5724 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main2.js @@ -0,0 +1,15 @@ +System.register(['./generated-dep.js'], (function (exports, module) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('main1', value); + module.import('./generated-dynamic1.js'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main3.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main3.js new file mode 100644 index 00000000000..a469eaf5724 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main3.js @@ -0,0 +1,15 @@ +System.register(['./generated-dep.js'], (function (exports, module) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('main1', value); + module.import('./generated-dynamic1.js'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main4.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main4.js new file mode 100644 index 00000000000..a469eaf5724 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/_expected/system/main4.js @@ -0,0 +1,15 @@ +System.register(['./generated-dep.js'], (function (exports, module) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.v; + }], + execute: (function () { + + console.log('main1', value); + module.import('./generated-dynamic1.js'); + + }) + }; +})); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dep.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dep.js new file mode 100644 index 00000000000..1d4be9c15d0 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dep.js @@ -0,0 +1 @@ +export const value = 'shared'; diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic1.js new file mode 100644 index 00000000000..58d0327fc60 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic1.js @@ -0,0 +1,4 @@ +import { value } from './dep.js'; +console.log('dynamic1', value); +import('./dynamic2.js'); + diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic2.js new file mode 100644 index 00000000000..daf24d9c4d3 --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/dynamic2.js @@ -0,0 +1,2 @@ +import { value } from './dep.js'; +console.log('dynamic2', value); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main1.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main1.js new file mode 100644 index 00000000000..170c4e5d89a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main1.js @@ -0,0 +1,3 @@ +import { value } from './dep.js'; +console.log('main1', value); +import('./dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main2.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main2.js new file mode 100644 index 00000000000..170c4e5d89a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main2.js @@ -0,0 +1,3 @@ +import { value } from './dep.js'; +console.log('main1', value); +import('./dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main3.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main3.js new file mode 100644 index 00000000000..170c4e5d89a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main3.js @@ -0,0 +1,3 @@ +import { value } from './dep.js'; +console.log('main1', value); +import('./dynamic1.js'); diff --git a/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main4.js b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main4.js new file mode 100644 index 00000000000..170c4e5d89a --- /dev/null +++ b/test/chunking-form/samples/improved-dynamic-chunks/cut-off/main4.js @@ -0,0 +1,3 @@ +import { value } from './dep.js'; +console.log('main1', value); +import('./dynamic1.js'); From acd21ab7a2cce21cd16f6035137223f86fbf87cb Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 10 Dec 2022 07:06:39 +0100 Subject: [PATCH 5/5] Use shared Set generator --- cli/run/batchWarnings.ts | 6 +++--- src/Chunk.ts | 6 +++--- src/Module.ts | 8 ++++---- src/ast/utils/PathTracker.ts | 4 ++-- src/utils/chunkAssignment.ts | 11 +++++++---- src/utils/getOrCreate.ts | 8 ++++++++ 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/cli/run/batchWarnings.ts b/cli/run/batchWarnings.ts index f394ea5d534..00fbf12798a 100644 --- a/cli/run/batchWarnings.ts +++ b/cli/run/batchWarnings.ts @@ -1,6 +1,6 @@ import type { RollupWarning } from '../../src/rollup/types'; import { bold, gray, yellow } from '../../src/utils/colors'; -import { getOrCreate } from '../../src/utils/getOrCreate'; +import { getNewArray, getOrCreate } from '../../src/utils/getOrCreate'; import { printQuotedStringList } from '../../src/utils/printStringList'; import relativeId from '../../src/utils/relativeId'; import { stderr } from '../logging'; @@ -23,7 +23,7 @@ export default function batchWarnings(): BatchWarnings { warningOccurred = true; if (warning.code! in deferredHandlers) { - getOrCreate(deferredWarnings, warning.code!, () => []).push(warning); + getOrCreate(deferredWarnings, warning.code!, getNewArray).push(warning); } else if (warning.code! in immediateHandlers) { immediateHandlers[warning.code!](warning); } else { @@ -220,7 +220,7 @@ const deferredHandlers: { const dependencies = new Map(); for (const warning of warnings) { - getOrCreate(dependencies, relativeId(warning.exporter!), () => []).push( + getOrCreate(dependencies, relativeId(warning.exporter!), getNewArray).push( relativeId(warning.id!) ); } diff --git a/src/Chunk.ts b/src/Chunk.ts index 88574c17152..2685737a9de 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -41,7 +41,7 @@ import { assignExportsToMangledNames, assignExportsToNames } from './utils/expor import type { GenerateCodeSnippets } from './utils/generateCodeSnippets'; import getExportMode from './utils/getExportMode'; import getIndentString from './utils/getIndentString'; -import { getOrCreate } from './utils/getOrCreate'; +import { getNewArray, getOrCreate } from './utils/getOrCreate'; import { getStaticDependencies } from './utils/getStaticDependencies'; import type { HashPlaceholderGenerator } from './utils/hashPlaceholders'; import { replacePlaceholders } from './utils/hashPlaceholders'; @@ -911,7 +911,7 @@ export default class Chunk { dependency = this.chunkByModule.get(module)!; imported = dependency.getVariableExportName(variable); } - getOrCreate(importsByDependency, dependency, () => []).push({ + getOrCreate(importsByDependency, dependency, getNewArray).push({ imported, local: variable.getName(this.snippets.getPropertyAccess) }); @@ -1023,7 +1023,7 @@ export default class Chunk { (imported !== 'default' || isDefaultAProperty(interop(module.id), true)); } } - getOrCreate(reexportSpecifiers, dependency, () => []).push({ + getOrCreate(reexportSpecifiers, dependency, getNewArray).push({ imported, needsLiveBinding, reexported: exportName diff --git a/src/Module.ts b/src/Module.ts index 173ce7f4125..4fdd738f3c3 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -63,7 +63,7 @@ import { warnDeprecation } from './utils/error'; import { getId } from './utils/getId'; -import { getOrCreate } from './utils/getOrCreate'; +import { getNewSet, getOrCreate } from './utils/getOrCreate'; import { getOriginalLocation } from './utils/getOriginalLocation'; import { makeLegal } from './utils/identifierHelpers'; import { @@ -164,10 +164,10 @@ function getVariableForExportNameRecursive( } function getAndExtendSideEffectModules(variable: Variable, module: Module): Set { - const sideEffectModules = getOrCreate( + const sideEffectModules = getOrCreate>( module.sideEffectDependenciesByVariable, variable, - () => new Set() + getNewSet ); let currentVariable: Variable | null = variable; const referencedVariables = new Set([currentVariable]); @@ -613,7 +613,7 @@ export default class Module { getOrCreate( importerForSideEffects.sideEffectDependenciesByVariable, variable, - () => new Set() + getNewSet ).add(this); setAlternativeExporterIfCyclic(variable, importerForSideEffects, this); } diff --git a/src/ast/utils/PathTracker.ts b/src/ast/utils/PathTracker.ts index e91742152ec..5c8aa4b5219 100644 --- a/src/ast/utils/PathTracker.ts +++ b/src/ast/utils/PathTracker.ts @@ -1,4 +1,4 @@ -import { getOrCreate } from '../../utils/getOrCreate'; +import { getNewSet, getOrCreate } from '../../utils/getOrCreate'; import type { Entity } from '../Entity'; export const UnknownKey = Symbol('Unknown Key'); @@ -98,7 +98,7 @@ export class DiscriminatedPathTracker { currentPaths[pathSegment] || Object.create(null, { [EntitiesKey]: { value: new Map>() } }); } - const trackedEntities = getOrCreate(currentPaths[EntitiesKey], discriminator, () => new Set()); + const trackedEntities = getOrCreate(currentPaths[EntitiesKey], discriminator, getNewSet); if (trackedEntities.has(entity)) return true; trackedEntities.add(entity); return false; diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index ad3ab13474e..45b43fc776c 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -1,6 +1,7 @@ import ExternalModule from '../ExternalModule'; import Module from '../Module'; -import { getOrCreate } from './getOrCreate'; +import { EMPTY_ARRAY } from './blank'; +import { getNewSet, getOrCreate } from './getOrCreate'; import { concatLazy } from './iterators'; import { timeEnd, timeStart } from './timers'; @@ -78,7 +79,7 @@ function analyzeModuleGraph(entries: Iterable): { for (const currentEntry of allEntries) { const modulesToHandle = new Set([currentEntry]); for (const module of modulesToHandle) { - getOrCreate(dependentEntriesByModule, module, () => new Set()).add(currentEntry); + getOrCreate(dependentEntriesByModule, module, getNewSet).add(currentEntry); for (const dependency of module.getDependenciesToBeIncluded()) { if (!(dependency instanceof ExternalModule)) { modulesToHandle.add(dependency); @@ -121,7 +122,7 @@ function getDynamicallyDependentEntriesByDynamicEntry( const dynamicallyDependentEntries = getOrCreate( dynamicallyDependentEntriesByDynamicEntry, dynamicEntry, - () => new Set() + getNewSet ); for (const importer of [ ...dynamicEntry.includedDynamicImporters, @@ -146,7 +147,7 @@ function assignEntryToStaticDependencies( const dynamicallyDependentEntries = dynamicallyDependentEntriesByDynamicEntry.get(entry); const modulesToHandle = new Set([entry]); for (const module of modulesToHandle) { - const assignedEntries = getOrCreate(assignedEntriesByModule, module, () => new Set()); + const assignedEntries = getOrCreate(assignedEntriesByModule, module, getNewSet); if ( dynamicallyDependentEntries && isModuleAlreadyLoaded( @@ -201,6 +202,8 @@ function isModuleAlreadyLoaded( return true; } +EMPTY_ARRAY; + interface ChunkDescription { alias: null; modules: Module[]; diff --git a/src/utils/getOrCreate.ts b/src/utils/getOrCreate.ts index e1f6157f3c1..ef75a8b4bd2 100644 --- a/src/utils/getOrCreate.ts +++ b/src/utils/getOrCreate.ts @@ -7,3 +7,11 @@ export function getOrCreate(map: Map, key: K, init: () => V): V { map.set(key, value); return value; } + +export function getNewSet() { + return new Set(); +} + +export function getNewArray(): T[] { + return []; +}