Repository: webpack/webpack (JavaScript)

Issue:
PreOrderIndex and PostOrderIndex are incorrect in some cases
<!-- Please don't delete this template because we'll close your issue -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->

# Bug report

Hi, I think I may find a issue about preOrderIndex, I don't know if this is a known issue

preOrderIndex and postOrderIndex are incorrect, in some cases, you can check it [here](https://github.com/JSerFeng/webpack-issue-order)

The reason here is the optimization by code splitting. Code splitting removes modules in chunk that are available in parents chunk.
For example, following chunk graph

![img_v3_027e_e5fb7e05-137c-4fd9-ad84-e6941ee7ae4g](https://github.com/webpack/webpack/assets/57202839/61c9871d-284c-4d45-8ddc-1c513d198dce)

chunk A and chunk shared both have module `m`, and from the chunk graph we can know `m`'s preOrderIndex is 1 and `open`'s preOrderIndex is 2.

But for optimization, webpack connect module and chunk as lazy as possible, and skip some module if that module presents in all parent chunk already.

When first visit shared chunk, webpack see only one parent A, so `m` is skipped because chunk A already has it.

When visit B-2, webpack know that B-2 is also parent of shared chunk, so webpack reconsider skipped modules, and connect `m` and `shared`, but this time, preOrderIndex is not the same in the first time, because `open` was connected, so in the end `open` has smaller preOrderIndex than `m`.

The main issue I found is CSS order, CSS sort based on postOrderIndex.

<!-- Please ask questions on StackOverflow or the GitHub Discussions. -->
<!-- https://github.com/webpack/webpack/discussions -->
<!-- https://stackoverflow.com/questions/ask?tags=webpack -->
<!-- Issues which contain questions or support requests will be closed. -->

**What is the current behavior?**

**If the current behavior is a bug, please provide the steps to reproduce.**

<!-- A great way to do this is to provide your configuration via a GitHub repository -->
<!-- The most helpful is a minimal reproduction with instructions on how to reproduce -->
<!-- Repositories with too many files or large `webpack.config.js` files are not suitable -->
<!-- Please only add small code snippets directly into this issue -->
<!-- https://gist.github.com is a good place for longer code snippets -->
<!-- If your issue is caused by a plugin or loader, please create an issue on the loader/plugin repository instead -->

**What is the expected behavior?**

`m` should have smaller preOrderIndex

<!-- "It should work" is not a helpful explanation -->
<!-- Explain exactly how it should behave -->

**Other relevant information:**
webpack version:  
Node.js version:
Operating System:
Additional tools:



Hints:

Patch:
diff --git a/lib/buildChunkGraph.js b/lib/buildChunkGraph.js
--- a/lib/buildChunkGraph.js
+++ b/lib/buildChunkGraph.js
@@ -266,12 +266,18 @@ const visitModules = (
 	/** @type {Map<DependenciesBlock, ChunkGroupInfo>} */
 	const blockChunkGroups = new Map();
 
+	/** @type {Map<ChunkGroupInfo, DependenciesBlock>} */
+	const blockByChunkGroups = new Map();
+
 	/** @type {Map<string, ChunkGroupInfo>} */
 	const namedChunkGroups = new Map();
 
 	/** @type {Map<string, ChunkGroupInfo>} */
 	const namedAsyncEntrypoints = new Map();
 
+	/** @type {Set<ChunkGroupInfo>} */
+	const outdatedOrderIndexChunkGroups = new Set();
+
 	const ADD_AND_ENTER_ENTRY_MODULE = 0;
 	const ADD_AND_ENTER_MODULE = 1;
 	const ENTER_MODULE = 2;
@@ -524,6 +530,7 @@ const visitModules = (
 				blockConnections.set(b, []);
 			}
 			blockChunkGroups.set(b, /** @type {ChunkGroupInfo} */ (cgi));
+			blockByChunkGroups.set(cgi, b);
 		} else if (entryOptions) {
 			entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
 		} else {
@@ -1208,6 +1215,7 @@ const visitModules = (
 					chunkGroupsForCombining.add(cgi);
 				}
 			}
+			outdatedOrderIndexChunkGroups.add(info);
 		}
 		outdatedChunkGroupInfo.clear();
 	};
@@ -1254,6 +1262,51 @@ const visitModules = (
 		}
 	}
 
+	for (const info of outdatedOrderIndexChunkGroups) {
+		const { chunkGroup, runtime } = info;
+
+		const block = blockByChunkGroups.get(info);
+
+		if (!block) {
+			continue;
+		}
+
+		let preOrderIndex = 0;
+		let postOrderIndex = 0;
+
+		const process = (current, visited = new Set()) => {
+			if (visited.has(current)) {
+				return;
+			}
+
+			visited.add(current);
+
+			const blockModules = getBlockModules(current, runtime);
+			if (blockModules === undefined) {
+				return;
+			}
+
+			for (let i = 0; i < blockModules.length; i += 2) {
+				const refModule = /** @type {Module} */ (blockModules[i]);
+				const activeState = /** @type {ConnectionState} */ (
+					blockModules[i + 1]
+				);
+				if (activeState === false) {
+					continue;
+				}
+
+				if (refModule) {
+					chunkGroup.setModulePreOrderIndex(refModule, preOrderIndex++);
+					process(refModule, visited);
+					chunkGroup.setModulePostOrderIndex(refModule, postOrderIndex++);
+				}
+			}
+		};
+
+		process(block);
+	}
+	outdatedOrderIndexChunkGroups.clear();
+
 	logger.log(
 		`${statProcessedQueueItems} queue items processed (${statProcessedBlocks} blocks)`
 	);


Test patch:
diff --git a/test/configCases/chunk-index/issue-18008/A.js b/test/configCases/chunk-index/issue-18008/A.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/A.js
@@ -0,0 +1,3 @@
+import './m.css'
+
+export default import(/* webpackChunkName: 'shared' */ './shared')
diff --git a/test/configCases/chunk-index/issue-18008/B-2.js b/test/configCases/chunk-index/issue-18008/B-2.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/B-2.js
@@ -0,0 +1 @@
+export default import(/* webpackChunkName: 'shared' */ './shared')
diff --git a/test/configCases/chunk-index/issue-18008/B.js b/test/configCases/chunk-index/issue-18008/B.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/B.js
@@ -0,0 +1 @@
+export default import(/* webpackChunkName: 'B-2' */ './B-2')
diff --git a/test/configCases/chunk-index/issue-18008/m.css b/test/configCases/chunk-index/issue-18008/m.css
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/m.css
@@ -0,0 +1,3 @@
+.m {
+  color: red;
+}
\ No newline at end of file
diff --git a/test/configCases/chunk-index/issue-18008/main.js b/test/configCases/chunk-index/issue-18008/main.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/main.js
@@ -0,0 +1,4 @@
+it('should compile', async () => {
+  await (await import(/* webpackChunkName: 'A' */ './A')).default
+  await (await import(/* webpackChunkName: 'B' */ './B')).default
+})
diff --git a/test/configCases/chunk-index/issue-18008/n.css b/test/configCases/chunk-index/issue-18008/n.css
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/n.css
@@ -0,0 +1,3 @@
+.n {
+  color: red;
+}
\ No newline at end of file
diff --git a/test/configCases/chunk-index/issue-18008/shared.js b/test/configCases/chunk-index/issue-18008/shared.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/shared.js
@@ -0,0 +1,2 @@
+import './m.css'
+import './n.css'
diff --git a/test/configCases/chunk-index/issue-18008/test.config.js b/test/configCases/chunk-index/issue-18008/test.config.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/test.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+	findBundle: function (i, options) {
+		return ["main.js", "A.js", "shared.js", "B.js", "B-2.js"];
+	}
+};
diff --git a/test/configCases/chunk-index/issue-18008/webpack.config.js b/test/configCases/chunk-index/issue-18008/webpack.config.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/issue-18008/webpack.config.js
@@ -0,0 +1,65 @@
+/** @typedef {import("../../../../types").Compilation} Compilation */
+/** @typedef {import("../../../../types").Module} Module */
+/** @type {import("../../../../types").Configuration} */
+module.exports = {
+	entry: {
+		main: "./main.js"
+	},
+	output: {
+		filename: "[name].js"
+	},
+	optimization: {
+		splitChunks: false,
+		chunkIds: "named"
+	},
+	plugins: [
+		function () {
+			/**
+			 * @param {Compilation} compilation compilation
+			 * @returns {void}
+			 */
+			const handler = compilation => {
+				compilation.hooks.afterSeal.tap("testcase", () => {
+					const data = {};
+					for (const [name, group] of compilation.namedChunkGroups) {
+						/** @type {Map<Module, number>} */
+						const modules = new Map();
+						for (const chunk of group.chunks) {
+							for (const module of compilation.chunkGraph.getChunkModulesIterable(
+								chunk
+							)) {
+								const preOrder = group.getModulePreOrderIndex(module);
+								if (typeof preOrder === "number") {
+									modules.set(module, preOrder);
+								}
+							}
+						}
+						const sortedModules = Array.from(modules).sort((a, b) => {
+							return a[1] - b[1];
+						});
+						const text = sortedModules
+							.map(
+								([m, index]) =>
+									`${index}: ${m.readableIdentifier(
+										compilation.requestShortener
+									)}`
+							)
+							.join(", ");
+						data[name + "Index"] = text;
+					}
+					expect(data).toEqual({
+						AIndex: "0: ./A.js, 1: css ./m.css",
+						"B-2Index": "0: ./B-2.js",
+						BIndex: "0: ./B.js",
+						mainIndex: "0: ./main.js",
+						sharedIndex: "0: ./shared.js, 1: css ./m.css, 2: css ./n.css"
+					});
+				});
+			};
+			this.hooks.compilation.tap("testcase", handler);
+		}
+	],
+	experiments: {
+		css: true
+	}
+};