Skip to content

CSS Module Order Changes When Using sideEffects: false with Barrel Files #18961

@jantimon

Description

@jantimon

Connected Issues

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

  1. The issue is specific to webpack 5 - other bundlers (rspack, vite, parcel) handle this correctly (see links in the end)
  2. The problem occurs when all these conditions are met:
    • Using barrel files (index.ts) for imports
    • Having sideEffects: false in package.json
    • Using CSS modules (probably also normal css)
    • Being in a monorepo setup
  3. 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:

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
Loading

Working Variations

There are two workarounds:

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:

  1. Having this import structure:
// @libraries/teaser/src/teaser.ts
import { CarouselButton } from '@segments/carousel'; // via barrel file
import styles from './teaser.module.css';
  1. Building with sideEffects: false produces 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 { ... }
  1. Building with "sideEffects": ["*.css"] produces incorrect CSS order ❌

See also main.css

.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; }  /* ❌ Should be last */
.R_y25aX9lTSLQtlxA1c9 { ... }
  1. Building with sideEffects: true produces correct order ✅

See also main.css

.R_y25aX9lTSLQtlxA1c9 { ... }
.hDE5PT5V3QGAPX9o9iZl { ... }
.yqrxTjAG22vkATE1VjR9 { background-color: orange; }
  1. Building without a barrel file with sideEffects: false also 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:

  • css built by rspack ✅
  • css built by vite ✅
  • css by parcel ✅

Environment

  • Webpack 5.96.1
  • Node.js >= 20
  • pnpm 8.15.4

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions