-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
Description
Connected Issues
- CSS Order Differs Between Development & Production Modes when Treeshaking. #7094 (CSS Order Differs Between Development & Production Modes when Treeshaking)
Description
When using CSS Modules in a monorepo setup with barrel files (index.ts) and sideEffects: false, webpack produces an incorrect CSS ordering in the output bundle. This might be connected to the long-standing CSS ordering issues with tree-shaking
Key Findings
- The issue is specific to webpack 5 - other bundlers (rspack, vite, parcel) handle this correctly (see links in the end)
- The problem occurs when all these conditions are met:
- Using barrel files (index.ts) for imports
- Having
sideEffects: falsein package.json - Using CSS modules (probably also normal css)
- Being in a monorepo setup
- Setting
"sideEffects": ["*.css"]does NOT fix the issue - it behaves the same as"sideEffects": false
After some debugging we traced it to the build chunk graph logic:
webpack/lib/buildChunkGraph.js
Lines 683 to 708 in 5e21745
| const refOrdinal = /** @type {number} */ getModuleOrdinal(refModule); | |
| const activeState = /** @type {ConnectionState} */ ( | |
| blockModules[i + 1] | |
| ); | |
| if (activeState !== true) { | |
| const connections = /** @type {ModuleGraphConnection[]} */ ( | |
| blockModules[i + 2] | |
| ); | |
| skipConnectionBuffer.push([refModule, connections]); | |
| // We skip inactive connections | |
| if (activeState === false) continue; | |
| } else if (isOrdinalSetInMask(minAvailableModules, refOrdinal)) { | |
| // already in parent chunks, skip it for now | |
| skipBuffer.push(refModule); | |
| continue; | |
| } | |
| // enqueue, then add and enter to be in the correct order | |
| // this is relevant with circular dependencies | |
| queueBuffer.push({ | |
| action: activeState === true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK, | |
| block: refModule, | |
| module: refModule, | |
| chunk, | |
| chunkGroup, | |
| chunkGroupInfo | |
| }); |
graph TD
subgraph "No Barrel ✅"
A3["@applications/base/src/index.ts preOrder: 0, postOrder: 6"]
B3["@libraries/teaser/src/teaser.ts preOrder: 1, postOrder: 5"]
E3["@segments/carousel/src/buttons.ts preOrder: 2, postOrder: 2"]
F3["@segments/carousel/src/button.module.css preOrder: 3, postOrder: 1"]
G3["button.module.css|0|||}} preOrder: 4, postOrder: 0"]
H3["@libraries/teaser/src/teaser.module.css preOrder: 5, postOrder: 4"]
I3["teaser.module.css|0|||}} preOrder: 6, postOrder: 3"]
A3 --> B3
B3 --> E3
E3 --> F3
F3 --> G3
B3 --> H3
H3 --> I3
style A3 fill:#0a0a4a,stroke:#333
style F3 fill:#294b51,stroke:#333
style G3 fill:#294b51,stroke:#333
style H3 fill:#294b51,stroke:#333
style I3 fill:#294b51,stroke:#333
end
subgraph "sideEffects: true ✅"
A2["@applications/base/src/index.ts preOrder: 0, postOrder: 8"]
B2["@libraries/teaser/src/index.ts preOrder: 1, postOrder: 7"]
C2["@libraries/teaser/src/teaser.ts preOrder: 2, postOrder: 6"]
D2["@segments/carousel/src/index.ts preOrder: 3, postOrder: 3"]
E2["@segments/carousel/src/buttons.ts preOrder: 4, postOrder: 2"]
F2["@segments/carousel/src/button.module.css preOrder: 5, postOrder: 1"]
G2["button.module.css|0|||}} preOrder: 6, postOrder: 0"]
H2["@libraries/teaser/src/teaser.module.css preOrder: 7, postOrder: 5"]
I2["teaser.module.css|0|||}} preOrder: 8, postOrder: 4"]
A2 --> B2
B2 --> C2
C2 --> D2
D2 --> E2
E2 --> F2
F2 --> G2
C2 --> H2
H2 --> I2
style A2 fill:#0a0a4a,stroke:#333
style F2 fill:#294b51,stroke:#333
style G2 fill:#294b51,stroke:#333
style H2 fill:#294b51,stroke:#333
style I2 fill:#294b51,stroke:#333
end
subgraph "sideEffects: false ❌"
A1["@applications/base/src/index.ts preOrder: 0, postOrder: 6"]
B1["@libraries/teaser/src/teaser.ts preOrder: 1, postOrder: 5"]
C1["@libraries/teaser/src/teaser.module.css preOrder: 2, postOrder: 1"]
D1["teaser.module.css|0|||}} preOrder: 3, postOrder: 0"]
E1["@segments/carousel/src/buttons.ts preOrder: 4, postOrder: 4"]
F1["@segments/carousel/src/button.module.css preOrder: 5, postOrder: 3"]
G1["button.module.css|0|||}} preOrder: 6, postOrder: 2"]
A1 --> B1
B1 --> C1
C1 --> D1
B1 --> E1
E1 --> F1
F1 --> G1
style A1 fill:#0a0a4a,stroke:#333
style C1 fill:#294b51,stroke:#333
style D1 fill:#294b51,stroke:#333
style F1 fill:#294b51,stroke:#333
style G1 fill:#294b51,stroke:#333
end
Working Variations
There are two workarounds:
- Set
"sideEffects": truein package.json (unfortunately this will break chunk splitting)
OR
- Import directly from implementation file instead of barrel:
// Instead of: import { CarouselButton } from '@segments/carousel'; import { CarouselButton } from '@segments/carousel/buttons';
But I believe we should fix it in webpack because it works in all other bundlers I tried (rspack, vite, parcel)
Reproduction
Full reproduction repository: https://github.com/jantimon/reproduction-webpack-css-order
The issue can be reproduced by:
- Having this import structure:
// @libraries/teaser/src/teaser.ts
import { CarouselButton } from '@segments/carousel'; // via barrel file
import styles from './teaser.module.css';- Building with
sideEffects: falseproduces incorrect CSS order ❌
The repository has dist files checked in so you can see the full main.css
.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; } /* ❌ Should be last */
.R_y25aX9lTSLQtlxA1c9 { ... }- Building with
"sideEffects": ["*.css"]produces incorrect CSS order ❌
See also main.css
.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; } /* ❌ Should be last */
.R_y25aX9lTSLQtlxA1c9 { ... }- Building with
sideEffects: trueproduces correct order ✅
See also main.css
.R_y25aX9lTSLQtlxA1c9 { ... }
.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; }- Building without a barrel file with
sideEffects: falsealso produces correct order ✅
See also main.css
.R_y25aX9lTSLQtlxA1c9 { ... }
.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; }Comparison with Other Bundlers
Each bundler repository branch contains the same code structure and dependencies, only changing the build configuration for the respective bundler:
Environment
- Webpack 5.96.1
- Node.js >= 20
- pnpm 8.15.4