Repository: webpack/webpack (JavaScript)

Issue:
@import CSS at-rule not always respected when determining order
<!-- 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

<!-- 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?**

Despite `a.css` being imported by `b.css`, it comes later in the resulting css file.

**If the current behavior is a bug, please provide the steps to reproduce.**

https://github.com/Hypnosphi/webpack-css-order

```
npm install
npm run build
```

<!-- 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?**

`a.css` should go before `b.css` because it's imported. The [spec](https://drafts.csswg.org/css-cascade-5/#at-import) explicitly states that the imported rules should be treated as if they were written in place of the `@import` rule.

It works OK in 5.90.0. The issue is probably caused by https://github.com/webpack/webpack/pull/18009

<!-- "It should work" is not a helpful explanation -->
<!-- Explain exactly how it should behave -->

**Other relevant information:**
webpack version:  5.90.3
Node.js version: 18.18.2
Operating System: macOS



Hints:
Hi there , I would like to work on this issue , I can't access this page though https://github.com/Hypnosphi/webpack-css-order , getting 404
Sorry, it should be public now
/cc @JSerFeng friendly ping

/cc @ahabhgk How do rspack solve this problem?
@Hypnosphi Anyway I recommend to avoid rely on CSS order when you have multiple `import ... from ...` and `@import`, there are a lot of limitations
I believe the same problem exists in Rspack. I will look into this

Patch:
diff --git a/lib/buildChunkGraph.js b/lib/buildChunkGraph.js
--- a/lib/buildChunkGraph.js
+++ b/lib/buildChunkGraph.js
@@ -1134,12 +1134,6 @@ const visitModules = (
 		 * @param {BlocksWithNestedBlocks} visited visited dependencies blocks
 		 */
 		const process = (current, visited) => {
-			if (visited.has(current)) {
-				return;
-			}
-
-			visited.add(current);
-
 			const blockModules = getBlockModules(current, runtime);
 			if (blockModules === undefined) {
 				return;
@@ -1153,6 +1147,11 @@ const visitModules = (
 					continue;
 				}
 				const refModule = /** @type {Module} */ (blockModules[i]);
+				if (visited.has(refModule)) {
+					continue;
+				}
+
+				visited.add(refModule);
 
 				if (refModule) {
 					chunkGroup.setModulePreOrderIndex(refModule, preOrderIndex++);


Test patch:
diff --git a/test/configCases/chunk-index/recalc-index/a.css b/test/configCases/chunk-index/recalc-index/a.css
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/recalc-index/a.css
@@ -0,0 +1,3 @@
+.a {
+  color: red;
+}
diff --git a/test/configCases/chunk-index/recalc-index/b.css b/test/configCases/chunk-index/recalc-index/b.css
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/recalc-index/b.css
@@ -0,0 +1,5 @@
+@import './a.css';
+
+.b {
+  color: blue;
+}
diff --git a/test/configCases/chunk-index/recalc-index/dynamic.js b/test/configCases/chunk-index/recalc-index/dynamic.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/recalc-index/dynamic.js
@@ -0,0 +1,2 @@
+import './b.css'
+import './a.css'
diff --git a/test/configCases/chunk-index/recalc-index/index.js b/test/configCases/chunk-index/recalc-index/index.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/recalc-index/index.js
@@ -0,0 +1,3 @@
+it('should compile', async () => {
+	await import(/* webpackChunkName: 'dynamic' */ './dynamic')
+})
diff --git a/test/configCases/chunk-index/recalc-index/webpack.config.js b/test/configCases/chunk-index/recalc-index/webpack.config.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/chunk-index/recalc-index/webpack.config.js
@@ -0,0 +1,55 @@
+/** @typedef {import("../../../../types").Compilation} Compilation */
+/** @typedef {import("../../../../types").Module} Module */
+/** @type {import("../../../../types").Configuration} */
+module.exports = {
+	entry: {
+		main: "./index.js"
+	},
+	experiments: {
+		css: true
+	},
+	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 postOrder = group.getModulePostOrderIndex(module);
+								if (typeof postOrder === "number") {
+									modules.set(module, postOrder);
+								}
+							}
+						}
+						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({
+						dynamicIndex: "0: css ./a.css, 1: css ./b.css, 2: ./dynamic.js",
+						mainIndex: "0: ./index.js"
+					});
+				});
+			};
+			this.hooks.compilation.tap("testcase", handler);
+		}
+	]
+};