Skip to content

Commit 4baab4e

Browse files
authored
feat: optimize updateParent by batching dependency sorting (#20204)
1 parent 90b4b01 commit 4baab4e

File tree

5 files changed

+71
-69
lines changed

5 files changed

+71
-69
lines changed

.changeset/fast-parents-sort.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": minor
3+
---
4+
5+
Optimize dependency sorting in updateParent: sort each module only once by deferring to finishUpdateParent(), and reduce traversal count in sortWithSourceOrder by caching WeakMap values upfront.

lib/ModuleGraph.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ class ModuleGraph {
169169
* @private
170170
*/
171171
this._dependencySourceOrderMap = new WeakMap();
172+
173+
/**
174+
* @type {Set<Module>}
175+
* @private
176+
*/
177+
this._modulesNeedingSort = new Set();
172178
}
173179

174180
/**
@@ -330,17 +336,28 @@ class ModuleGraph {
330336
sub: originSourceOrder
331337
});
332338

339+
// Save for later batch sorting
340+
this._modulesNeedingSort.add(parentModule);
341+
}
342+
}
343+
344+
/**
345+
* @returns {void}
346+
*/
347+
finishUpdateParent() {
348+
if (this._modulesNeedingSort.size === 0) {
349+
return;
350+
}
351+
for (const mod of this._modulesNeedingSort) {
333352
// If dependencies like HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency have a SourceOrder,
334353
// we sort based on it; otherwise, we preserve the original order.
335354
sortWithSourceOrder(
336-
parentModule.dependencies,
337-
this._dependencySourceOrderMap
355+
mod.dependencies,
356+
this._dependencySourceOrderMap,
357+
(dep, index) => this.setParentDependenciesBlockIndex(dep, index)
338358
);
339-
340-
for (const [index, dep] of parentModule.dependencies.entries()) {
341-
this.setParentDependenciesBlockIndex(dep, index);
342-
}
343359
}
360+
this._modulesNeedingSort.clear();
344361
}
345362

346363
/**

lib/optimize/SideEffectsFlagPlugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ class SideEffectsFlagPlugin {
381381
for (const module of modules) {
382382
optimizeIncomingConnections(module);
383383
}
384+
moduleGraph.finishUpdateParent();
384385
logger.timeEnd("update dependencies");
385386
}
386387
);

lib/util/comparators.js

Lines changed: 39 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -511,85 +511,62 @@ const compareChunksNatural = (chunkGraph) => {
511511
* https://github.com/webpack/webpack/pull/19686
512512
* @param {Dependency[]} dependencies dependencies
513513
* @param {WeakMap<Dependency, DependencySourceOrder>} dependencySourceOrderMap dependency source order map
514+
* @param {((dep: Dependency, index: number) => void)=} onDependencyReSort optional callback to set index for each dependency
514515
* @returns {void}
515516
*/
516-
const sortWithSourceOrder = (dependencies, dependencySourceOrderMap) => {
517-
/**
518-
* @param {Dependency} dep dependency
519-
* @returns {number} source order
520-
*/
521-
const getSourceOrder = (dep) => {
522-
if (dependencySourceOrderMap.has(dep)) {
523-
const { main } = /** @type {DependencySourceOrder} */ (
524-
dependencySourceOrderMap.get(dep)
525-
);
526-
return main;
527-
}
528-
return /** @type {number} */ (
529-
/** @type {ModuleDependency} */ (dep).sourceOrder
530-
);
531-
};
532-
533-
/**
534-
* If the sourceOrder is a number, it means the dependency needs to be sorted.
535-
* @param {number | undefined} sourceOrder sourceOrder
536-
* @returns {boolean} needReSort
537-
*/
538-
const needReSort = (sourceOrder) => {
539-
if (typeof sourceOrder === "number") {
540-
return true;
541-
}
542-
return false;
543-
};
544-
545-
// Extract dependencies with sourceOrder and sort them
517+
const sortWithSourceOrder = (
518+
dependencies,
519+
dependencySourceOrderMap,
520+
onDependencyReSort
521+
) => {
522+
/** @type {{dep: Dependency, main: number, sub: number}[]} */
546523
const withSourceOrder = [];
524+
/** @type {number[]} */
525+
const positions = [];
547526

548-
// First pass: collect dependencies with sourceOrder
549527
for (let i = 0; i < dependencies.length; i++) {
550528
const dep = dependencies[i];
551-
const sourceOrder = getSourceOrder(dep);
552-
553-
if (needReSort(sourceOrder)) {
554-
withSourceOrder.push({ dep, sourceOrder, originalIndex: i });
529+
const cached = dependencySourceOrderMap.get(dep);
530+
531+
if (cached) {
532+
positions.push(i);
533+
withSourceOrder.push({
534+
dep,
535+
main: cached.main,
536+
sub: cached.sub
537+
});
538+
} else {
539+
const sourceOrder = /** @type {number | undefined} */ (
540+
/** @type {ModuleDependency} */ (dep).sourceOrder
541+
);
542+
if (typeof sourceOrder === "number") {
543+
positions.push(i);
544+
withSourceOrder.push({
545+
dep,
546+
main: sourceOrder,
547+
sub: 0
548+
});
549+
}
555550
}
556551
}
557552

558553
if (withSourceOrder.length <= 1) {
559554
return;
560555
}
561556

562-
// Sort dependencies with sourceOrder
563557
withSourceOrder.sort((a, b) => {
564-
// Handle both dependencies in map case
565-
if (
566-
dependencySourceOrderMap.has(a.dep) &&
567-
dependencySourceOrderMap.has(b.dep)
568-
) {
569-
const { main: mainA, sub: subA } = /** @type {DependencySourceOrder} */ (
570-
dependencySourceOrderMap.get(a.dep)
571-
);
572-
const { main: mainB, sub: subB } = /** @type {DependencySourceOrder} */ (
573-
dependencySourceOrderMap.get(b.dep)
574-
);
575-
if (mainA === mainB) {
576-
return compareNumbers(subA, subB);
577-
}
578-
return compareNumbers(mainA, mainB);
558+
if (a.main !== b.main) {
559+
return compareNumbers(a.main, b.main);
579560
}
580-
581-
return compareNumbers(a.sourceOrder, b.sourceOrder);
561+
return compareNumbers(a.sub, b.sub);
582562
});
583563

584-
// Second pass: build result array
585-
let sortedIndex = 0;
586-
for (let i = 0; i < dependencies.length; i++) {
587-
const dep = dependencies[i];
588-
const sourceOrder = getSourceOrder(dep);
589-
590-
if (needReSort(sourceOrder)) {
591-
dependencies[i] = withSourceOrder[sortedIndex].dep;
592-
sortedIndex++;
564+
// Second pass: place sorted deps back to original positions
565+
for (let i = 0; i < positions.length; i++) {
566+
const depIndex = positions[i];
567+
dependencies[depIndex] = withSourceOrder[i].dep;
568+
if (onDependencyReSort) {
569+
onDependencyReSort(dependencies[depIndex], depIndex);
593570
}
594571
}
595572
};

types.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10704,6 +10704,7 @@ declare class ModuleGraph {
1070410704
connection?: ModuleGraphConnection,
1070510705
parentModule?: Module
1070610706
): void;
10707+
finishUpdateParent(): void;
1070710708
removeConnection(dependency: Dependency): void;
1070810709
addExplanation(dependency: Dependency, explanation: string): void;
1070910710
cloneModuleAttributes(sourceModule: Module, targetModule: Module): void;
@@ -19357,7 +19358,8 @@ declare namespace exports {
1935719358
export let keepOriginalOrder: <T>(iterable: Iterable<T>) => Comparator<T>;
1935819359
export let sortWithSourceOrder: (
1935919360
dependencies: Dependency[],
19360-
dependencySourceOrderMap: WeakMap<Dependency, DependencySourceOrder>
19361+
dependencySourceOrderMap: WeakMap<Dependency, DependencySourceOrder>,
19362+
onDependencyReSort?: (dep: Dependency, index: number) => void
1936119363
) => void;
1936219364
}
1936319365
export namespace runtime {

0 commit comments

Comments
 (0)