Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Compilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ const { isSourceEqual } = require("./util/source");
* @property {boolean=} development true, when asset is only used for development and doesn't count towards user-facing assets
* @property {boolean=} hotModuleReplacement true, when asset ships data for updating an existing application (HMR)
* @property {boolean=} javascriptModule true, when asset is javascript and an ESM
* @property {Record<string, string | string[]>=} related object of pointers to other assets, keyed by type of relation (only points from parent to child)
* @property {Record<string, null | string | string[]>=} related object of pointers to other assets, keyed by type of relation (only points from parent to child)
*/

/** @typedef {KnownAssetInfo & Record<string, EXPECTED_ANY>} AssetInfo */
Expand Down
50 changes: 18 additions & 32 deletions lib/ModuleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const ExportsInfo = require("./ExportsInfo");
const ModuleGraphConnection = require("./ModuleGraphConnection");
const SortableSet = require("./util/SortableSet");
const WeakTupleMap = require("./util/WeakTupleMap");
const { compareNumbers, compareSelect } = require("./util/comparators");
const { sortWithSourceOrder } = require("./util/comparators");

/** @typedef {import("./Compilation").ModuleMemCaches} ModuleMemCaches */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
Expand All @@ -22,6 +22,7 @@ const { compareNumbers, compareSelect } = require("./util/comparators");
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("./dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
/** @typedef {import("./dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
/** @typedef {import("./util/comparators").DependencySourceOrder} DependencySourceOrder */

/**
* @callback OptimizationBailoutFunction
Expand All @@ -31,17 +32,6 @@ const { compareNumbers, compareSelect } = require("./util/comparators");

const EMPTY_SET = new Set();

/**
* @param {number} num the input number (should be less than or equal to total)
* @param {number} total the total number used to determine decimal places
* @returns {number} the decimal representation of num
*/
function numberToDecimal(num, total) {
const totalDigitCount = total.toString().length;
const divisor = 10 ** totalDigitCount;
return num / divisor;
}

/**
* @param {SortableSet<ModuleGraphConnection>} set input
* @returns {readonly Map<Module | undefined, readonly ModuleGraphConnection[]>} mapped by origin module
Expand Down Expand Up @@ -174,7 +164,7 @@ class ModuleGraph {
this._cacheStage = undefined;

/**
* @type {WeakMap<Dependency, number>}
* @type {WeakMap<Dependency, DependencySourceOrder>}
* @private
*/
this._dependencySourceOrderMap = new WeakMap();
Expand Down Expand Up @@ -308,14 +298,17 @@ class ModuleGraph {
return;
}
const originDependency = connection.dependency;

// src/index.js
// import { c } from "lib/c" -> c = 0
// import { a, b } from "lib": a and b have the same source order -> a = b = 1
// import { a, b } from "lib" -> a and b have the same source order -> a = b = 1
// import { d } from "lib/d" -> d = 2
const currentSourceOrder =
/** @type { HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
dependency
).sourceOrder;
// lib/index.js

// lib/index.js (reexport)
// import { a } from "lib/a" -> a = 0
// import { b } from "lib/b" -> b = 1
const originSourceOrder =
Expand All @@ -328,26 +321,19 @@ class ModuleGraph {
) {
// src/index.js
// import { c } from "lib/c" -> c = 0
// import { a } from "lib/a" -> a = 1 + 0.0
// import { b } from "lib/b" -> b = 1 + 0.1
const newSourceOrder =
currentSourceOrder +
numberToDecimal(originSourceOrder, parentModule.dependencies.length);

this._dependencySourceOrderMap.set(dependency, newSourceOrder);
// import { a } from "lib/a" -> a = 1.0 = 1(main) + 0.0(sub)
// import { b } from "lib/b" -> b = 1.1 = 1(main) + 0.1(sub)
// import { d } from "lib/d" -> d = 2
this._dependencySourceOrderMap.set(dependency, {
main: currentSourceOrder,
sub: originSourceOrder
});

// If dependencies like HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency have a SourceOrder,
// we sort based on it; otherwise, we preserve the original order.
parentModule.dependencies.sort(
compareSelect(
a =>
this._dependencySourceOrderMap.has(a)
? this._dependencySourceOrderMap.get(a)
: /** @type { HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
a
).sourceOrder,
compareNumbers
)
sortWithSourceOrder(
parentModule.dependencies,
this._dependencySourceOrderMap
);

for (const [index, dep] of parentModule.dependencies.entries()) {
Expand Down
15 changes: 3 additions & 12 deletions lib/NormalModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ const { isSubset } = require("./util/SetHelpers");
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const {
compareLocations,
compareNumbers,
compareSelect,
concatComparators,
keepOriginalOrder
keepOriginalOrder,
sortWithSourceOrder
} = require("./util/comparators");
const createHash = require("./util/createHash");
const { createFakeHook } = require("./util/deprecation");
Expand Down Expand Up @@ -1220,20 +1220,11 @@ class NormalModule extends Module {
const handleParseResult = () => {
this.dependencies.sort(
concatComparators(
// For HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency, we should prioritize import order to match the behavior of running modules directly in a JS engine without a bundler.
// For other types like ConstDependency, we can instead prioritize usage order.
// https://github.com/webpack/webpack/pull/19686
compareSelect(
a =>
/** @type {HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
a
).sourceOrder,
compareNumbers
),
compareSelect(a => a.loc, compareLocations),
keepOriginalOrder(this.dependencies)
)
);
sortWithSourceOrder(this.dependencies, new WeakMap());
this._initBuildHash(compilation);
this._lastSuccessfulBuildMeta =
/** @type {BuildMeta} */
Expand Down
4 changes: 2 additions & 2 deletions lib/asset/AssetGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
/**
* @template T
* @template U
* @param {string | Array<T> | Set<T> | undefined} a a
* @param {string | Array<U> | Set<U> | undefined} b b
* @param {null | string | Array<T> | Set<T> | undefined} a a
* @param {null | string | Array<U> | Set<U> | undefined} b b
* @returns {Array<T> & Array<U>} array
*/
const mergeMaybeArrays = (a, b) => {
Expand Down
1 change: 1 addition & 0 deletions lib/stats/DefaultStatsFactoryPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ const SIMPLE_EXTRACTORS = {
? relatedEntry
: [relatedEntry];
for (const dep of deps) {
if (!dep) continue;
const depItem = assetMap.get(dep);
if (!depItem) continue;
assets.delete(depItem);
Expand Down
99 changes: 99 additions & 0 deletions lib/util/comparators.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,18 @@ const { compareRuntime } = require("./runtime");
/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
/** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */

/**
* @typedef {object} DependencySourceOrder
* @property {number} main the main source order
* @property {number} sub the sub source order
*/

/**
* @template T
* @typedef {(a: T, b: T) => -1 | 0 | 1} Comparator
Expand Down Expand Up @@ -497,6 +506,95 @@ const compareChunksNatural = chunkGraph => {
);
};

/**
* For HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency, we should prioritize import order to match the behavior of running modules directly in a JS engine without a bundler.
* For other types like ConstDependency, we can instead prioritize usage order.
* https://github.com/webpack/webpack/pull/19686
* @param {Dependency[]} dependencies dependencies
* @param {WeakMap<Dependency, DependencySourceOrder>} dependencySourceOrderMap dependency source order map
* @returns {void}
*/
const sortWithSourceOrder = (dependencies, dependencySourceOrderMap) => {
/**
* @param {Dependency} dep dependency
* @returns {number} source order
*/
const getSourceOrder = dep => {
if (dependencySourceOrderMap.has(dep)) {
const { main } = /** @type {DependencySourceOrder} */ (
dependencySourceOrderMap.get(dep)
);
return main;
}
return /** @type { HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
dep
).sourceOrder;
};

/**
* If the sourceOrder is a number, it means the dependency needs to be sorted.
* @param {number | undefined} sourceOrder sourceOrder
* @returns {boolean} needReSort
*/
const needReSort = sourceOrder => {
if (typeof sourceOrder === "number") {
return true;
}
return false;
};

// Extract dependencies with sourceOrder and sort them
const withSourceOrder = [];

// First pass: collect dependencies with sourceOrder
for (let i = 0; i < dependencies.length; i++) {
const dep = dependencies[i];
const sourceOrder = getSourceOrder(dep);

if (needReSort(sourceOrder)) {
withSourceOrder.push({ dep, sourceOrder, originalIndex: i });
}
}

if (withSourceOrder.length === 0) {
return;
}

// Sort dependencies with sourceOrder
withSourceOrder.sort((a, b) => {
// Handle both dependencies in map case
if (
dependencySourceOrderMap.has(a.dep) &&
dependencySourceOrderMap.has(b.dep)
) {
const { main: mainA, sub: subA } = /** @type {DependencySourceOrder} */ (
dependencySourceOrderMap.get(a.dep)
);
const { main: mainB, sub: subB } = /** @type {DependencySourceOrder} */ (
dependencySourceOrderMap.get(b.dep)
);
if (mainA === mainB) {
return compareNumbers(subA, subB);
}
return compareNumbers(mainA, mainB);
}

return compareNumbers(a.sourceOrder, b.sourceOrder);
});

// Second pass: build result array
let sortedIndex = 0;
for (let i = 0; i < dependencies.length; i++) {
const dep = dependencies[i];
const sourceOrder = getSourceOrder(dep);

if (needReSort(sourceOrder)) {
dependencies[i] = withSourceOrder[sortedIndex].dep;
sortedIndex++;
}
}
};

module.exports.compareChunkGroupsByIndex = compareChunkGroupsByIndex;
/** @type {ParameterizedComparator<ChunkGraph, Chunk>} */
module.exports.compareChunks =
Expand Down Expand Up @@ -548,3 +646,4 @@ module.exports.compareStringsNumeric = compareStringsNumeric;
module.exports.concatComparators = concatComparators;

module.exports.keepOriginalOrder = keepOriginalOrder;
module.exports.sortWithSourceOrder = sortWithSourceOrder;
2 changes: 2 additions & 0 deletions test/__snapshots__/ConfigCacheTestCases.longtest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3500,6 +3500,8 @@ exports[`ConfigCacheTestCases css css-order exported tests keep consistent css o

exports[`ConfigCacheTestCases css css-order2 exported tests keep consistent css order 1`] = `".dependency2::before { content: \\"dependency2\\";}.dependency::before { content: \\"dependency\\";}"`;

exports[`ConfigCacheTestCases css css-order3 exported tests keep consistent css order 1`] = `".dependency3::before { content: \\"dependency3\\";}.dependency2::before { content: \\"dependency2\\";}.dependency::before { content: \\"dependency\\";}"`;

exports[`ConfigCacheTestCases css escape-unescape exported tests should work with URLs in CSS: classes 1`] = `
Object {
"#": "_style_modules_css-#",
Expand Down
2 changes: 2 additions & 0 deletions test/__snapshots__/ConfigTestCases.basictest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3500,6 +3500,8 @@ exports[`ConfigTestCases css css-order exported tests keep consistent css order

exports[`ConfigTestCases css css-order2 exported tests keep consistent css order 1`] = `".dependency2::before { content: \\"dependency2\\";}.dependency::before { content: \\"dependency\\";}"`;

exports[`ConfigTestCases css css-order3 exported tests keep consistent css order 1`] = `".dependency3::before { content: \\"dependency3\\";}.dependency2::before { content: \\"dependency2\\";}.dependency::before { content: \\"dependency\\";}"`;

exports[`ConfigTestCases css escape-unescape exported tests should work with URLs in CSS: classes 1`] = `
Object {
"#": "_style_modules_css-#",
Expand Down
Loading
Loading