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
91 changes: 91 additions & 0 deletions lib/ModuleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +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");

/** @typedef {import("./Compilation").ModuleMemCaches} ModuleMemCaches */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
Expand All @@ -19,6 +20,8 @@ const WeakTupleMap = require("./util/WeakTupleMap");
/** @typedef {import("./ModuleProfile")} ModuleProfile */
/** @typedef {import("./RequestShortener")} RequestShortener */
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("./dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
/** @typedef {import("./dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */

/**
* @callback OptimizationBailoutFunction
Expand All @@ -28,6 +31,17 @@ const WeakTupleMap = require("./util/WeakTupleMap");

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 @@ -158,6 +172,12 @@ class ModuleGraph {
* @private
*/
this._cacheStage = undefined;

/**
* @type {WeakMap<Dependency, number>}
* @private
*/
this._dependencySourceOrderMap = new WeakMap();
}

/**
Expand Down Expand Up @@ -186,6 +206,15 @@ class ModuleGraph {
dependency._parentModule = module;
}

/**
* @param {Dependency} dependency the dependency
* @param {number} index the index
* @returns {void}
*/
setParentDependenciesBlockIndex(dependency, index) {
dependency._parentDependenciesBlockIndex = index;
}

/**
* @param {Dependency} dependency the dependency
* @returns {Module | undefined} parent module
Expand Down Expand Up @@ -265,6 +294,68 @@ class ModuleGraph {
targetMgm.incomingConnections.add(newConnection);
}

/**
* @param {Dependency} dependency the need update dependency
* @param {ModuleGraphConnection=} connection the target connection
* @param {Module=} parentModule the parent module
* @returns {void}
*/
updateParent(dependency, connection, parentModule) {
if (this._dependencySourceOrderMap.has(dependency)) {
return;
}
if (!connection || !parentModule) {
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
const currentSourceOrder =
/** @type { HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
dependency
).sourceOrder;
// lib/index.js
// import { a } from "lib/a" -> a = 0
// import { b } from "lib/b" -> b = 1
const originSourceOrder =
/** @type { HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */ (
originDependency
).sourceOrder;
if (
typeof currentSourceOrder === "number" &&
typeof originSourceOrder === "number"
) {
// 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);

// 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
)
);

for (const [index, dep] of parentModule.dependencies.entries()) {
this.setParentDependenciesBlockIndex(dep, index);
}
}
}

/**
* @param {Dependency} dependency the referencing dependency
* @returns {void}
Expand Down
13 changes: 13 additions & 0 deletions lib/NormalModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const { isSubset } = require("./util/SetHelpers");
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const {
compareLocations,
compareNumbers,
compareSelect,
concatComparators,
keepOriginalOrder
Expand Down Expand Up @@ -99,6 +100,8 @@ const memoize = require("./util/memoize");
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
/** @typedef {import("./dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
/** @typedef {import("./dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
/**
* @template T
* @typedef {import("./util/deprecation").FakeHook<T>} FakeHook
Expand Down Expand Up @@ -1217,6 +1220,16 @@ 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)
)
Expand Down
7 changes: 7 additions & 0 deletions lib/optimize/SideEffectsFlagPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ class SideEffectsFlagPlugin {
if (!target) continue;

moduleGraph.updateModule(dep, target.module);
moduleGraph.updateParent(
dep,
/** @type {ModuleGraphConnection} */ (
target.connection
),
/** @type {Module} */ (connection.originModule)
);
moduleGraph.addExplanation(
dep,
"(skipped side-effect-free modules)"
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/ConfigCacheTestCases.longtest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3496,6 +3496,10 @@ exports[`ConfigCacheTestCases css css-modules-no-space exported tests should all
"
`;

exports[`ConfigCacheTestCases css css-order exported tests keep consistent css order 1`] = `".button-module { padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px;}.teaser-module { padding: 20px; border: 1px solid #ddd; border-radius: 8px; margin: 16px;}.teaser-module { background-color: orange;}"`;

exports[`ConfigCacheTestCases css css-order2 exported tests keep consistent css order 1`] = `".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
4 changes: 4 additions & 0 deletions test/__snapshots__/ConfigTestCases.basictest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3496,6 +3496,10 @@ exports[`ConfigTestCases css css-modules-no-space exported tests should allow to
"
`;

exports[`ConfigTestCases css css-order exported tests keep consistent css order 1`] = `".button-module { padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px;}.teaser-module { padding: 20px; border: 1px solid #ddd; border-radius: 8px; margin: 16px;}.teaser-module { background-color: orange;}"`;

exports[`ConfigTestCases css css-order2 exported tests keep consistent css order 1`] = `".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
8 changes: 4 additions & 4 deletions test/__snapshots__/StatsTestCases.basictest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3446,8 +3446,8 @@ cacheable modules X bytes
./first.js X bytes [built] [code generated]
./second.js X bytes [built] [code generated]
./vendor.js X bytes [built] [code generated]
./module_first.js X bytes [built] [code generated]
./common2.js X bytes [built] [code generated]
./module_first.js X bytes [built] [code generated]
./lazy_first.js X bytes [built] [code generated]
./lazy_shared.js X bytes [built] [code generated]
./lazy_second.js X bytes [built] [code generated]
Expand All @@ -3473,8 +3473,8 @@ cacheable modules X bytes
ModuleConcatenation bailout: Cannot concat with ./common_lazy_shared.js: Module ./common_lazy_shared.js is referenced from different chunks by these modules: ./lazy_shared.js
./common_lazy_shared.js X bytes [built] [code generated]
orphan modules X bytes [orphan]
./module_first.js X bytes [orphan] [built]
./common2.js X bytes [orphan] [built]
./module_first.js X bytes [orphan] [built]
./common.js X bytes [orphan] [built]
ModuleConcatenation bailout: Module is not in any chunk
./common_lazy.js X bytes [orphan] [built]
Expand Down Expand Up @@ -3548,10 +3548,10 @@ cacheable modules X KiB
| [no exports]
| [no exports used]
| Statement (ExpressionStatement) with side effects in source code at 4:0-30
| ./node_modules/module-with-export/index.js X KiB [built]
| [only some exports used: smallVar]
| ./node_modules/big-module/a.js X bytes [built]
| [only some exports used: a]
| ./node_modules/module-with-export/index.js X KiB [built]
| [only some exports used: smallVar]
./node_modules/module-with-export/emptyModule.js X bytes [built] [code generated]
[used exports unknown]
ModuleConcatenation bailout: Module is not an ECMAScript module
Expand Down
15 changes: 15 additions & 0 deletions test/configCases/css/css-order/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Teaser } from "./liba";

document.body.innerHTML = Teaser();

// https://github.com/webpack/webpack/issues/18961
// https://github.com/jantimon/reproduction-webpack-css-order
it("keep consistent css order", function() {
const fs = __non_webpack_require__("fs");
let source = fs.readFileSync(__dirname + "/main.css", "utf-8");
expect(removeComments(source)).toMatchSnapshot()
});

function removeComments(source) {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n/g, "");
}
13 changes: 13 additions & 0 deletions test/configCases/css/css-order/liba/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CarouselButton } from '../libb';
import styles from './teaser.module.css';

export const Teaser = () => {
return `
<div class="${styles.teaser}">
<h2>Teaser Component</h2>
${CarouselButton({
className: styles.teaserCarouselButton,
})}
</div>
`;
};
1 change: 1 addition & 0 deletions test/configCases/css/css-order/liba/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './common';
10 changes: 10 additions & 0 deletions test/configCases/css/css-order/liba/teaser.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.teaser {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 16px;
}

.teaserCarouselButton {
background-color: orange;
}
7 changes: 7 additions & 0 deletions test/configCases/css/css-order/libb/button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.button {
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
}
9 changes: 9 additions & 0 deletions test/configCases/css/css-order/libb/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styles from './button.module.css';

export const CarouselButton = ({
className = '',
}) => {
return `<button class="${styles.button + (
className ? ` ${className}` : ''
)}">Carousel Button</button>`;
};
1 change: 1 addition & 0 deletions test/configCases/css/css-order/libb/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './common';
8 changes: 8 additions & 0 deletions test/configCases/css/css-order/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "css-order",
"version": "1.0.0",
"sideEffects": false,
"devDependencies": {
"mini-css-extract-plugin": "^2.9.0"
}
}
43 changes: 43 additions & 0 deletions test/configCases/css/css-order/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

/** @type {import("../../../../").Configuration} */
module.exports = {
devtool: false,
target: "web",
entry: "./index.js",
mode: "development",
optimization: {
concatenateModules: false
},
module: {
rules: [
{
test: /\.module\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: "css-loader",
options: {
esModule: true,
modules: {
namedExport: false,
localIdentName: "[name]"
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css"
})
],
node: {
__dirname: false,
__filename: false
}
};
6 changes: 6 additions & 0 deletions test/configCases/css/css-order2/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { dependency, dependency2 } from "./dependency";

export function component() {
dependency();
dependency2();
}
3 changes: 3 additions & 0 deletions test/configCases/css/css-order2/dependency/dependency.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dependency::before {
content: "dependency";
}
5 changes: 5 additions & 0 deletions test/configCases/css/css-order2/dependency/dependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styles from "./dependency.css";

export function dependency() {
return styles !== undefined;
}
3 changes: 3 additions & 0 deletions test/configCases/css/css-order2/dependency/dependency2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dependency2::before {
content: "dependency2";
}
5 changes: 5 additions & 0 deletions test/configCases/css/css-order2/dependency/dependency2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styles from "./dependency2.css";

export function dependency2() {
return styles !== undefined;
}
2 changes: 2 additions & 0 deletions test/configCases/css/css-order2/dependency/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./dependency2";
export * from "./dependency";
7 changes: 7 additions & 0 deletions test/configCases/css/css-order2/dependency/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "dependency",
"version": "1.0.0",
"private": true,
"sideEffects": false,
"main": "index.js"
}
Loading
Loading