Repository: microsoft/griffel
Pull request #381 (2023-06-27 10:28:50)

feat(webpack-extraction-plugin): add experimental_resetModuleIndexes
This PR adds `experimental_resetModuleIndexes` to avoid warnings caused by `mini-css-extract-plugin`:

```
WARNING in chunk griffel [mini-css-extract-plugin]
Conflicting order. Following module has been added:
 * css ./node_modules/css-loader/dist/cjs.js!./node_modules/@griffel/webpack-extraction-plugin/virtual-loader/index.js?style=%2F**%20%40griffel%3Acss-start%20%5Bd%5D%20**%2F%0A.fm40iov%7Bcolor%3A%23ccc%3B%7D%0A%2F**%20%40griffel%3Acss-end%20**%2F%0A!./src/foo-module/baz.js
despite it was not able to fulfill desired ordering with these modules:
 * css ./node_modules/css-loader/dist/cjs.js!./node_modules/@griffel/webpack-extraction-plugin/virtual-loader/index.js?style=%2F**%20%40griffel%3Acss-start%20%5Bd%5D%20**%2F%0A.f1e30ogq%7Bcolor%3Ablueviolet%3B%7D%0A%2F**%20%40griffel%3Acss-end%20**%2F%0A!./src/foo-module/qux.js
   - couldn't fulfill desired order of chunk group(s) 
```

github-actions[bot]: ## 📊 Bundle size report

<sub>🤖 This report was generated against <a href='https://github.com/microsoft/griffel/commit/5ea341a4b388d65f344c86e9877f9aa93f369d4c'>5ea341a4b388d65f344c86e9877f9aa93f369d4c</a></sub>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/change/@griffel-webpack-extraction-plugin-f133a3fe-d85a-429a-b88e-77633804e494.json b/change/@griffel-webpack-extraction-plugin-f133a3fe-d85a-429a-b88e-77633804e494.json
new file mode 100644
index 0000000..889b022
--- /dev/null
+++ b/change/@griffel-webpack-extraction-plugin-f133a3fe-d85a-429a-b88e-77633804e494.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "feat: add experimental_resetModuleIndexes",
+  "packageName": "@griffel/webpack-extraction-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentA.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentA.ts
new file mode 100644
index 0000000..d3e28e0
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentA.ts
@@ -0,0 +1,6 @@
+import { useBarClasses } from './useBarStyles';
+import { useBazClasses } from './useBazStyles';
+
+export function ComponentA() {
+  return [useBazClasses(), useBarClasses()];
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentB.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentB.ts
new file mode 100644
index 0000000..a28d05c
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/ComponentB.ts
@@ -0,0 +1,6 @@
+import { useBarClasses } from './useBarStyles';
+import { useQuxClasses } from './useQuxStyles';
+
+export function ComponentB() {
+  return [useQuxClasses(), useBarClasses()];
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk1.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk1.ts
new file mode 100644
index 0000000..027577b
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk1.ts
@@ -0,0 +1,6 @@
+import { ComponentA } from './ComponentA';
+import { ComponentB } from './ComponentB';
+
+export function App() {
+  return [ComponentA(), ComponentB()];
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk2.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk2.ts
new file mode 100644
index 0000000..4556acb
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/chunk2.ts
@@ -0,0 +1,6 @@
+import { ComponentB } from './ComponentB';
+import { ComponentA } from './ComponentA';
+
+export function App() {
+  return [ComponentA(), ComponentB()];
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/code.ts
new file mode 100644
index 0000000..fe07c45
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/code.ts
@@ -0,0 +1,6 @@
+export async function lazy() {
+  const styles1 = await import(/* webpackChunkName: "chunk1" */ './chunk1');
+  const styles2 = await import(/* webpackChunkName: "chunk2" */ './chunk2');
+
+  return [styles1, styles2];
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/fs.json
new file mode 100644
index 0000000..73744c9
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "chunk1.js", "chunk2.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/output.css
new file mode 100644
index 0000000..f1412ed
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/output.css
@@ -0,0 +1,13 @@
+/** griffel.css **/
+.fe3e8s9 {
+  color: red;
+}
+.fycuoez {
+  padding-left: 4px;
+}
+.f8wuabp {
+  padding-right: 4px;
+}
+.fcnqdeg {
+  background-color: green;
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBarStyles.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBarStyles.ts
new file mode 100644
index 0000000..78296ce
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBarStyles.ts
@@ -0,0 +1,8 @@
+import { __styles } from '@griffel/react';
+
+export const useBarClasses = __styles(
+  {
+    root: { sj55zd: 'fe3e8s9' },
+  },
+  { d: ['.fe3e8s9{color:red;}'] },
+);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBazStyles.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBazStyles.ts
new file mode 100644
index 0000000..c776c15
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useBazStyles.ts
@@ -0,0 +1,12 @@
+import { __styles } from '@griffel/react';
+
+export const useBazClasses = __styles(
+  {
+    root: {
+      uwmqm3: ['fycuoez', 'f8wuabp'],
+    },
+  },
+  {
+    d: ['.fycuoez{padding-left:4px;}', '.f8wuabp{padding-right:4px;}'],
+  },
+);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useQuxStyles.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useQuxStyles.ts
new file mode 100644
index 0000000..35f13b5
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/with-chunks-order/useQuxStyles.ts
@@ -0,0 +1,12 @@
+import { __styles } from '@griffel/react';
+
+export const useQuxClasses = __styles(
+  {
+    root: {
+      De3pzq: 'fcnqdeg',
+    },
+  },
+  {
+    d: ['.fcnqdeg{background-color:green;}'],
+  },
+);
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
index 38135c4..e644318 100644
--- a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
@@ -11,6 +11,7 @@ const OPTIMIZE_CHUNKS_STAGE_ADVANCED = 10;
 
 export type GriffelCSSExtractionPluginOptions = {
   compareMediaQueries?: GriffelRenderer['compareMediaQueries'];
+  experimental_resetModuleIndexes?: boolean;
   unstable_attachToMainEntryPoint?: boolean;
 };
 
@@ -93,16 +94,50 @@ function moveCSSModulesToGriffelChunk(compilation: Compilation) {
   }
 }
 
+/**
+ * Updates indexes on chunk groups to follow module order. This makes order of CSS modules in chunks consistent in
+ * chunk groups and prevents warnings in mini-css-extract-plugin.
+ */
+function updateCSSModulePostIndexes(compilation: Compilation): void {
+  const griffelChunk = compilation.namedChunks.get('griffel');
+
+  if (typeof griffelChunk === 'undefined') {
+    return;
+  }
+
+  // https://github.com/webpack-contrib/mini-css-extract-plugin/blob/26334462e419026086856787d672b052cd916c62/src/index.js#L693-L697
+  const cssModules = compilation.chunkGraph.getChunkModulesIterableBySourceType(griffelChunk, 'css/mini-extract');
+
+  if (typeof cssModules === 'undefined') {
+    return;
+  }
+
+  for (const cssModule of cssModules) {
+    if (!isCSSModule(cssModule)) {
+      continue;
+    }
+
+    for (const group of griffelChunk.groupsIterable) {
+      group.setModulePostOrderIndex(cssModule, compilation.moduleGraph.getPostOrderIndex(cssModule));
+    }
+  }
+}
+
 export class GriffelCSSExtractionPlugin {
   static loader = require.resolve('./webpackLoader');
 
   private readonly attachToMainEntryPoint: NonNullable<
     GriffelCSSExtractionPluginOptions['unstable_attachToMainEntryPoint']
   >;
+  private readonly resetModuleIndexes: NonNullable<
+    GriffelCSSExtractionPluginOptions['experimental_resetModuleIndexes']
+  >;
   private readonly compareMediaQueries: NonNullable<GriffelCSSExtractionPluginOptions['compareMediaQueries']>;
 
   constructor(options?: GriffelCSSExtractionPluginOptions) {
     this.attachToMainEntryPoint = options?.unstable_attachToMainEntryPoint ?? false;
+    this.resetModuleIndexes = options?.experimental_resetModuleIndexes ?? false;
+
     this.compareMediaQueries = options?.compareMediaQueries ?? defaultCompareMediaQueries;
   }
 
@@ -159,12 +194,14 @@ export class GriffelCSSExtractionPlugin {
         if (this.attachToMainEntryPoint) {
           const griffelChunk = compilation.namedChunks.get('griffel');
 
-          if (typeof griffelChunk === 'undefined') {
-            return;
+          if (typeof griffelChunk !== 'undefined') {
+            griffelChunk.disconnectFromGroups();
+            attachGriffelChunkToMainEntryPoint(compilation, griffelChunk);
           }
+        }
 
-          griffelChunk.disconnectFromGroups();
-          attachGriffelChunkToMainEntryPoint(compilation, griffelChunk);
+        if (this.resetModuleIndexes) {
+          updateCSSModulePostIndexes(compilation);
         }
       });
 


Test patch:
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
index f7f0262..e1fc94b 100644
--- a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
@@ -67,7 +67,7 @@ async function compileSourceWithWebpack(
       ],
     },
     plugins: [
-      new GriffelCSSExtractionPlugin(),
+      new GriffelCSSExtractionPlugin(options.pluginOptions),
       new MiniCssExtractPlugin({
         filename: options.cssFilename ?? '[name].css',
       }),
@@ -106,6 +106,11 @@ async function compileSourceWithWebpack(
         return;
       }
 
+      if (stats.hasWarnings()) {
+        reject(stats.toJson().warnings![0]);
+        return;
+      }
+
       const filesList = (virtualFsVolume.readdirSync(__dirname) as string[]).sort();
       const cssOutput = filesList
         .filter(filename => filename.includes('.css'))
@@ -220,6 +225,7 @@ function testFixture(fixtureName: string, options: CompileOptions = {}) {
 
 describe('webpackLoader', () => {
   // Basic assertions
+  // --------------------
   testFixture('basic-rules');
   testFixture('reset');
   testFixture('reset-media');
@@ -235,10 +241,14 @@ describe('webpackLoader', () => {
   testFixture('style-buckets');
 
   // Assets
+  // --------------------
   testFixture('assets');
   testFixture('assets-multiple');
   testFixture('reset-assets');
 
+  // Config
+  // --------------------
+
   // Custom filenames in mini-css-extract-plugin
   testFixture('config-name', { cssFilename: '[name].[contenthash].css' });
 
@@ -269,6 +279,9 @@ describe('webpackLoader', () => {
     },
   });
 
+  // Compatibility
+  // --------------------
+
   // "pathinfo" adds comments with paths to output
   testFixture('basic-rules', { webpackConfig: { output: { pathinfo: true } } });
 
@@ -276,9 +289,16 @@ describe('webpackLoader', () => {
   testFixture('with-css');
 
   // Chunks
+  // --------------------
   testFixture('with-chunks');
 
+  // A fixture that creates a different modules order in different chunks groups
+  testFixture('with-chunks-order', {
+    pluginOptions: { experimental_resetModuleIndexes: true },
+  });
+
   // Unstable
+  // --------------------
   testFixture('unstable-attach-to-main', {
     pluginOptions: { unstable_attachToMainEntryPoint: true },
     webpackConfig: {