Repository: microsoft/griffel
Pull request #147 (2022-06-21 11:38:02)

feat: add Webpack plugin for CSS extraction
This PR adds Webpack plugin to `@griffel/webpack-extraction-plugin`.

The plugin aggregates all `griffel.css` imports created by Webpack loader to a single asset via `optimization.splitChunks.cacheGroups`. Code splitting is not supported i.e. all CSS rules emitted by Griffel will go to a single chunk (that asset).

The sorting and deduplication of rules will be implemented in a follow up PR.

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

<details>
<summary>Unchanged fixtures</summary>

| Package & Exports                                                                                                            |        Size (minified/GZIP) |
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------: |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStyles.fixture.js'>makeStyles + mergeClasses (build time)</abbr>       |       `1.8 kB`<br />`861 B` |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStylesRuntime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr>   | `20.394 kB`<br />`7.612 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStaticStyles-runtime.fixture.js'>makeStaticStyles (runtime)</abbr>    |  `8.638 kB`<br />`3.771 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles-runtime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr> | `21.816 kB`<br />`8.202 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles.fixture.js'>makeStyles + mergeClasses (build time)</abbr>      |    `3.2 kB`<br />`1.448 kB` |

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

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/apps/website/src/components/Playground/code/app.js b/apps/website/src/components/Playground/code/app.js
index 12edae0..5342f21 100644
--- a/apps/website/src/components/Playground/code/app.js
+++ b/apps/website/src/components/Playground/code/app.js
@@ -24,6 +24,7 @@ export default function App() {
       },
       insertionCache: {},
       stylesheets: {},
+      compareMediaQueries: () => 1,
     };
 
     styles({
diff --git a/change/@griffel-core-37ff1b21-9fd5-4dc1-8bb9-673f0af5c5f0.json b/change/@griffel-core-37ff1b21-9fd5-4dc1-8bb9-673f0af5c5f0.json
deleted file mode 100644
index 78f6128..0000000
--- a/change/@griffel-core-37ff1b21-9fd5-4dc1-8bb9-673f0af5c5f0.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "minor",
-  "comment": "feat: Create isomorphic stylesheet for DOM renderer",
-  "packageName": "@griffel/core",
-  "email": "lingfangao@hotmail.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-core-a35241dc-0448-4b79-938e-f7d7b860b324.json b/change/@griffel-core-a35241dc-0448-4b79-938e-f7d7b860b324.json
deleted file mode 100644
index b8aa7e9..0000000
--- a/change/@griffel-core-a35241dc-0448-4b79-938e-f7d7b860b324.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "patch",
-  "comment": "collect devtools data when webpack loader is used",
-  "packageName": "@griffel/core",
-  "email": "yuanboxue@microsoft.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-core-b0216f7b-4524-4ecc-ae6b-a2d7f1493caf.json b/change/@griffel-core-b0216f7b-4524-4ecc-ae6b-a2d7f1493caf.json
deleted file mode 100644
index 7ec46bc..0000000
--- a/change/@griffel-core-b0216f7b-4524-4ecc-ae6b-a2d7f1493caf.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "minor",
-  "comment": "feat: Insert media queries into separate buckets based on the media query value",
-  "packageName": "@griffel/core",
-  "email": "lingfangao@hotmail.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-core-facf601f-c750-49b7-a293-125df7470f78.json b/change/@griffel-core-facf601f-c750-49b7-a293-125df7470f78.json
deleted file mode 100644
index 21a0445..0000000
--- a/change/@griffel-core-facf601f-c750-49b7-a293-125df7470f78.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "minor",
-  "comment": "Adds shorthands support for flex property.",
-  "packageName": "@griffel/core",
-  "email": "riacarmin@microsoft.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-react-4dd6ac38-fca5-4652-be5b-6b3ff5709fdb.json b/change/@griffel-react-4dd6ac38-fca5-4652-be5b-6b3ff5709fdb.json
deleted file mode 100644
index 021a7a3..0000000
--- a/change/@griffel-react-4dd6ac38-fca5-4652-be5b-6b3ff5709fdb.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "minor",
-  "comment": "feat: Update renderToStyleElement to use isomorphic stylesheets from renderer",
-  "packageName": "@griffel/react",
-  "email": "lingfangao@hotmail.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-react-dcbfe547-31b2-46aa-891f-5de203376148.json b/change/@griffel-react-dcbfe547-31b2-46aa-891f-5de203376148.json
deleted file mode 100644
index ce8b782..0000000
--- a/change/@griffel-react-dcbfe547-31b2-46aa-891f-5de203376148.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "patch",
-  "comment": "chore: Rename instances of styleElement to stylesheet where approriate following [#137](https://github.com/microsoft/griffel/pull/137)",
-  "packageName": "@griffel/react",
-  "email": "lingfangao@hotmail.com",
-  "dependentChangeType": "patch"
-}
diff --git a/package.json b/package.json
index 07dc0e3..d0f03df 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
     "@types/jest": "27.0.2",
     "@types/js-beautify": "^1.13.3",
     "@types/loader-utils": "2.0.3",
+    "@types/mini-css-extract-plugin": "2.5.1",
     "@types/node": "14.14.33",
     "@types/react": "17.0.30",
     "@types/react-dom": "17.0.9",
@@ -87,6 +88,7 @@
     "jest": "27.2.3",
     "js-beautify": "^1.14.0",
     "log-symbols": "5.1.0",
+    "mini-css-extract-plugin": "2.6.0",
     "monosize": "0.0.3",
     "monosize-storage-upstash": "0.0.2",
     "nano-staged": "0.5.0",
diff --git a/packages/babel-preset/CHANGELOG.json b/packages/babel-preset/CHANGELOG.json
index 5665d6f..bcc17bf 100644
--- a/packages/babel-preset/CHANGELOG.json
+++ b/packages/babel-preset/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/babel-preset",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/babel-preset_v1.3.2",
+      "version": "1.3.2",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/babel-preset",
+            "comment": "Bump @griffel/core to v1.4.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ]
+      }
+    },
     {
       "date": "Tue, 17 May 2022 08:34:48 GMT",
       "tag": "@griffel/babel-preset_v1.3.1",
diff --git a/packages/babel-preset/CHANGELOG.md b/packages/babel-preset/CHANGELOG.md
index 0871cf7..5d8e0b6 100644
--- a/packages/babel-preset/CHANGELOG.md
+++ b/packages/babel-preset/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/babel-preset
 
-This log was last generated on Tue, 17 May 2022 08:34:48 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.3.2
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.0
+
 ## 1.3.1
 
 Tue, 17 May 2022 08:34:48 GMT
diff --git a/packages/babel-preset/package.json b/packages/babel-preset/package.json
index 41f7fcc..bd3be17 100644
--- a/packages/babel-preset/package.json
+++ b/packages/babel-preset/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/babel-preset",
-  "version": "1.3.1",
+  "version": "1.3.2",
   "description": "Babel preset with build time transforms for Griffel",
   "license": "MIT",
   "repository": {
@@ -19,6 +19,6 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.3.1"
+    "@griffel/core": "^1.4.0"
   }
 }
diff --git a/packages/core/.eslintrc.json b/packages/core/.eslintrc.json
index 707d8fb..82b5d5e 100644
--- a/packages/core/.eslintrc.json
+++ b/packages/core/.eslintrc.json
@@ -1,6 +1,6 @@
 {
   "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
-  "ignorePatterns": ["!**/*"],
+  "ignorePatterns": ["!**/*", "__fixtures__/**/*/output.ts"],
   "overrides": [
     {
       "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
diff --git a/packages/core/CHANGELOG.json b/packages/core/CHANGELOG.json
index bedcc4c..a86b963 100644
--- a/packages/core/CHANGELOG.json
+++ b/packages/core/CHANGELOG.json
@@ -1,6 +1,53 @@
 {
   "name": "@griffel/core",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/core_v1.4.0",
+      "version": "1.4.0",
+      "comments": {
+        "minor": [
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/core",
+            "commit": "4e4c7c29deae40ae64bd3b6860356d0adeb05e8e",
+            "comment": "feat: create isomorphic stylesheet for DOM renderer"
+          },
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/core",
+            "commit": "5897290e8065eb7631770593944ea194cbced029",
+            "comment": "feat: add `compareMediaQueries` option to DOM renderer"
+          },
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/core",
+            "commit": "307d727163986ff0bb818a76b2186888622da985",
+            "comment": "feat: insert media queries into separate buckets based on the media query value"
+          },
+          {
+            "author": "riacarmin@microsoft.com",
+            "package": "@griffel/core",
+            "commit": "69f7a31db6383e2b41c185314672fe74945f914e",
+            "comment": "feat: add shorthands support for flex property"
+          }
+        ],
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/core",
+            "commit": "d516a9aa3b52032ca3419ad5824109394643296b",
+            "comment": "chore: add __css() functions for upcoming CSS extraction"
+          },
+          {
+            "author": "yuanboxue@microsoft.com",
+            "package": "@griffel/core",
+            "commit": "ba29eb7d7242d8446ce6e084c309eead8bd14cf3",
+            "comment": "fix: collect devtools data in __styles"
+          }
+        ]
+      }
+    },
     {
       "date": "Tue, 17 May 2022 08:34:48 GMT",
       "tag": "@griffel/core_v1.3.1",
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 1ab72d7..a277175 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,9 +1,25 @@
 # Change Log - @griffel/core
 
-This log was last generated on Tue, 17 May 2022 08:34:48 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.4.0
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Minor changes
+
+- feat: create isomorphic stylesheet for DOM renderer (lingfangao@hotmail.com)
+- feat: add `compareMediaQueries` option to DOM renderer (lingfangao@hotmail.com)
+- feat: insert media queries into separate buckets based on the media query value (lingfangao@hotmail.com)
+- feat: add shorthands support for flex property (riacarmin@microsoft.com)
+
+### Patches
+
+- chore: add __css() functions for upcoming CSS extraction (olfedias@microsoft.com)
+- fix: collect devtools data in __styles (yuanboxue@microsoft.com)
+
 ## 1.3.1
 
 Tue, 17 May 2022 08:34:48 GMT
diff --git a/packages/core/package.json b/packages/core/package.json
index 59a0a0a..951bf27 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/core",
-  "version": "1.3.1",
+  "version": "1.4.0",
   "description": "DOM implementation of Atomic CSS-in-JS",
   "license": "MIT",
   "repository": {
diff --git a/packages/core/src/__css.ts b/packages/core/src/__css.ts
new file mode 100644
index 0000000..2542969
--- /dev/null
+++ b/packages/core/src/__css.ts
@@ -0,0 +1,40 @@
+import { debugData, isDevToolsEnabled } from './devtools';
+import { reduceToClassNameForSlots } from './runtime/reduceToClassNameForSlots';
+import { MakeStylesOptions, CSSClassesMapBySlot } from './types';
+
+/**
+ * A version of makeStyles() that accepts build output as an input and skips all runtime transforms & DOM insertion.
+ *
+ * @internal
+ */
+export function __css<Slots extends string>(classesMapBySlot: CSSClassesMapBySlot<Slots>) {
+  let ltrClassNamesForSlots: Record<Slots, string> | null = null;
+  let rtlClassNamesForSlots: Record<Slots, string> | null = null;
+
+  function computeClasses(options: Pick<MakeStylesOptions, 'dir'>): Record<Slots, string> {
+    const { dir } = options;
+    const isLTR = dir === 'ltr';
+
+    if (isLTR) {
+      if (ltrClassNamesForSlots === null) {
+        ltrClassNamesForSlots = reduceToClassNameForSlots(classesMapBySlot, dir);
+      }
+    } else {
+      if (rtlClassNamesForSlots === null) {
+        rtlClassNamesForSlots = reduceToClassNameForSlots(classesMapBySlot, dir);
+      }
+    }
+
+    const classNamesForSlots = isLTR
+      ? (ltrClassNamesForSlots as Record<Slots, string>)
+      : (rtlClassNamesForSlots as Record<Slots, string>);
+
+    if (process.env.NODE_ENV !== 'production' && isDevToolsEnabled) {
+      debugData.addSequenceDetails(classNamesForSlots!);
+    }
+
+    return classNamesForSlots;
+  }
+
+  return computeClasses;
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 0de3503..cf7b0e9 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -49,6 +49,8 @@ export { makeStyles } from './makeStyles';
 export { resolveStyleRulesForSlots } from './resolveStyleRulesForSlots';
 
 // Private exports, are used by build time transforms
+export { __css } from './__css';
+export { reduceToClassNameForSlots } from './runtime/reduceToClassNameForSlots';
 export { resolveStyleRules } from './runtime/resolveStyleRules';
 export { __styles } from './__styles';
 
diff --git a/packages/core/src/renderer/createDOMRenderer.ts b/packages/core/src/renderer/createDOMRenderer.ts
index cbae030..cb12d0e 100644
--- a/packages/core/src/renderer/createDOMRenderer.ts
+++ b/packages/core/src/renderer/createDOMRenderer.ts
@@ -22,8 +22,17 @@ export interface CreateDOMRendererOptions {
    * ⚠️ This API is unstable and can be removed from the library at any time.
    */
   unstable_filterCSSRule?: (cssRule: string) => boolean;
+
+  /**
+   * @param a - media query
+   * @param b - media query
+   * @returns positive number if a > b or negative number if a < b
+   */
+  compareMediaQueries?: (a: string, b: string) => number;
 }
 
+const defaultCompareMediaQueries = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
+
 /**
  * Creates a new instances of a renderer.
  *
@@ -33,10 +42,11 @@ export function createDOMRenderer(
   target: Document | undefined = typeof document === 'undefined' ? undefined : document,
   options: CreateDOMRendererOptions = {},
 ): GriffelRenderer {
-  const { unstable_filterCSSRule } = options;
+  const { unstable_filterCSSRule, compareMediaQueries = defaultCompareMediaQueries } = options;
   const renderer: GriffelRenderer = {
     insertionCache: {},
     stylesheets: {},
+    compareMediaQueries,
 
     id: `d${lastIndex++}`,
 
diff --git a/packages/core/src/renderer/getStyleSheetForBucket.ts b/packages/core/src/renderer/getStyleSheetForBucket.ts
index de8134e..631d0fc 100644
--- a/packages/core/src/renderer/getStyleSheetForBucket.ts
+++ b/packages/core/src/renderer/getStyleSheetForBucket.ts
@@ -1,3 +1,4 @@
+import { DATA_BUCKET_ATTR } from '../constants';
 import { GriffelRenderer, IsomorphicStyleSheet, StyleBucketName } from '../types';
 import { createIsomorphicStyleSheet } from './createIsomorphicStyleSheet';
 
@@ -31,6 +32,12 @@ export const styleBucketOrdering: StyleBucketName[] = [
   'm',
 ];
 
+// avoid repeatedly calling `indexOf`to determine order during new insertions
+const styleBucketOrderingMap = styleBucketOrdering.reduce((acc, cur, j) => {
+  acc[cur as StyleBucketName] = j;
+  return acc;
+}, {} as Record<StyleBucketName, number>);
+
 /**
  * Lazily adds a `<style>` bucket to the `<head>`. This will ensure that the style buckets are ordered.
  */
@@ -58,21 +65,55 @@ export function getStyleSheetForBucket(
     renderer.stylesheets[stylesheetKey] = stylesheet;
 
     if (target && tag) {
-      let currentBucketIndex = styleBucketOrdering.indexOf(bucketName) + 1;
-      let nextBucketFromCache = null;
+      const elementSibling = findElementSibling(target, bucketName, renderer, metadata);
+      target.head.insertBefore(tag, elementSibling);
+    }
+  }
 
-      // Find the next bucket which we will add our new style bucket before.
-      for (; currentBucketIndex < styleBucketOrdering.length; currentBucketIndex++) {
-        const nextBucket = renderer.stylesheets[styleBucketOrdering[currentBucketIndex]];
-        if (nextBucket) {
-          nextBucketFromCache = nextBucket;
-          break;
-        }
-      }
+  return renderer.stylesheets[stylesheetKey]!;
+}
 
-      target.head.insertBefore(tag, nextBucketFromCache?.element || null);
+/**
+ * Finds an element before which the new bucket style element should be inserted following the
+ * bucket sort order
+ *
+ * @param target - document
+ * @param targetBucket - The bucket that should be inserted to DOM
+ * @param renderer - Griffel renderer
+ * @param metadata - metadata for CSS rule
+ * @returns - Smallest style element with greater sort order than the current bucket
+ */
+function findElementSibling(
+  target: Document,
+  targetBucket: StyleBucketName,
+  renderer: GriffelRenderer,
+  metadata?: Record<string, unknown>,
+) {
+  const targetOrder = styleBucketOrderingMap[targetBucket];
+
+  // Similar to javascript sort comparators where
+  // a positive value is increasing sort order
+  // a negative value is decreasing sort order
+  let comparer: (el: HTMLStyleElement) => number = (el: HTMLStyleElement) =>
+    targetOrder - styleBucketOrderingMap[el.getAttribute(DATA_BUCKET_ATTR) as StyleBucketName];
+
+  let styleElements = target.head.querySelectorAll<HTMLStyleElement>(`[${DATA_BUCKET_ATTR}]`);
+
+  if (targetBucket === 'm' && metadata) {
+    const mediaElements = target.head.querySelectorAll<HTMLStyleElement>(`[${DATA_BUCKET_ATTR}="${targetBucket}"]`);
+    // only reduce the scope of the search and change comparer
+    // if there are other media buckets already on the page
+    if (mediaElements.length) {
+      styleElements = mediaElements;
+      comparer = (el: HTMLStyleElement) => renderer.compareMediaQueries(metadata['m'] as string, el.media);
     }
   }
 
-  return renderer.stylesheets[stylesheetKey]!;
+  for (const styleElement of styleElements) {
+    if (comparer(styleElement) < 0) {
+      return styleElement;
+    }
+  }
+
+  return null;
 }
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index e207fbd..77c87c2 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -119,6 +119,11 @@ export interface GriffelRenderer {
    * @private
    */
   insertCSSRules(cssRules: CSSRulesByBucket): void;
+
+  /**
+   * @private
+   */
+  compareMediaQueries(a: string, b: string): number;
 }
 
 /**
diff --git a/packages/devtools/CHANGELOG.json b/packages/devtools/CHANGELOG.json
index a388d9a..139e8ad 100644
--- a/packages/devtools/CHANGELOG.json
+++ b/packages/devtools/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/devtools",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/devtools_v0.0.1",
+      "version": "0.0.1",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/devtools",
+            "comment": "Bump @griffel/react to v1.2.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ]
+      }
+    },
     {
       "date": "Thu, 26 May 2022 15:06:34 GMT",
       "tag": "@griffel/devtools_v0.0.1",
diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md
index f38078f..41d7a03 100644
--- a/packages/devtools/CHANGELOG.md
+++ b/packages/devtools/CHANGELOG.md
@@ -1,11 +1,19 @@
 # Change Log - @griffel/devtools
 
-This log was last generated on Thu, 26 May 2022 15:06:34 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
 ## 0.0.1
 
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Patches
+
+- Bump @griffel/react to v1.2.0
+
+## 0.0.1
+
 Thu, 26 May 2022 15:06:34 GMT
 
 ### Patches
diff --git a/packages/devtools/package.json b/packages/devtools/package.json
index 4e1c459..6708bdd 100644
--- a/packages/devtools/package.json
+++ b/packages/devtools/package.json
@@ -10,7 +10,7 @@
   },
   "sideEffects": false,
   "dependencies": {
-    "@griffel/react": "^1.1.0",
+    "@griffel/react": "^1.2.0",
     "js-beautify": "^1.14.0",
     "prism-react-renderer": "1.2.1",
     "react-dom": ">=16.8.0 <18.0.0",
diff --git a/packages/devtools/tsconfig.json b/packages/devtools/tsconfig.json
index 846439c..122c858 100644
--- a/packages/devtools/tsconfig.json
+++ b/packages/devtools/tsconfig.json
@@ -2,7 +2,7 @@
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
     "target": "es2019",
-    "lib": ["es2019", "dom"],
+    "lib": ["es2019", "dom", "DOM.Iterable"],
     "jsx": "react",
     "allowJs": true,
     "esModuleInterop": true,
diff --git a/packages/jest-serializer/CHANGELOG.json b/packages/jest-serializer/CHANGELOG.json
index 2331773..f9d8d84 100644
--- a/packages/jest-serializer/CHANGELOG.json
+++ b/packages/jest-serializer/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/jest-serializer",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/jest-serializer_v1.0.6",
+      "version": "1.0.6",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/jest-serializer",
+            "comment": "Bump @griffel/core to v1.4.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ]
+      }
+    },
     {
       "date": "Tue, 17 May 2022 08:34:48 GMT",
       "tag": "@griffel/jest-serializer_v1.0.5",
diff --git a/packages/jest-serializer/CHANGELOG.md b/packages/jest-serializer/CHANGELOG.md
index c51903d..0ac21e2 100644
--- a/packages/jest-serializer/CHANGELOG.md
+++ b/packages/jest-serializer/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/jest-serializer
 
-This log was last generated on Tue, 17 May 2022 08:34:48 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.0.6
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.0
+
 ## 1.0.5
 
 Tue, 17 May 2022 08:34:48 GMT
diff --git a/packages/jest-serializer/package.json b/packages/jest-serializer/package.json
index 15a3c6a..c604ac1 100644
--- a/packages/jest-serializer/package.json
+++ b/packages/jest-serializer/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/jest-serializer",
-  "version": "1.0.5",
+  "version": "1.0.6",
   "description": "Jest serializer for Griffel",
   "license": "MIT",
   "repository": {
@@ -11,6 +11,6 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.3.1"
+    "@griffel/core": "^1.4.0"
   }
 }
diff --git a/packages/react/CHANGELOG.json b/packages/react/CHANGELOG.json
index 2b5bae7..53ab4bb 100644
--- a/packages/react/CHANGELOG.json
+++ b/packages/react/CHANGELOG.json
@@ -1,6 +1,47 @@
 {
   "name": "@griffel/react",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/react_v1.2.0",
+      "version": "1.2.0",
+      "comments": {
+        "minor": [
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/react",
+            "commit": "4e4c7c29deae40ae64bd3b6860356d0adeb05e8e",
+            "comment": "feat: update renderToStyleElement to use isomorphic stylesheets from renderer"
+          },
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/react",
+            "commit": "5897290e8065eb7631770593944ea194cbced029",
+            "comment": "feat: update style rehydration to sort media queries"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/react",
+            "comment": "Bump @griffel/core to v1.4.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ],
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/react",
+            "commit": "d516a9aa3b52032ca3419ad5824109394643296b",
+            "comment": "chore: add __css() functions for upcoming CSS extraction"
+          },
+          {
+            "author": "lingfangao@hotmail.com",
+            "package": "@griffel/react",
+            "commit": "307d727163986ff0bb818a76b2186888622da985",
+            "comment": "chore: rename instances of styleElement to stylesheet where appropriate following [#137](https://github.com/microsoft/griffel/pull/137)"
+          }
+        ]
+      }
+    },
     {
       "date": "Thu, 26 May 2022 15:06:34 GMT",
       "tag": "@griffel/react_v1.1.0",
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index f76fd04..8b426c6 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,9 +1,24 @@
 # Change Log - @griffel/react
 
-This log was last generated on Thu, 26 May 2022 15:06:34 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.2.0
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Minor changes
+
+- feat: update renderToStyleElement to use isomorphic stylesheets from renderer (lingfangao@hotmail.com)
+- feat: update style rehydration to sort media queries (lingfangao@hotmail.com)
+- Bump @griffel/core to v1.4.0
+
+### Patches
+
+- chore: add __css() functions for upcoming CSS extraction (olfedias@microsoft.com)
+- chore: rename instances of styleElement to stylesheet where appropriate following [#137](https://github.com/microsoft/griffel/pull/137) (lingfangao@hotmail.com)
+
 ## 1.1.0
 
 Thu, 26 May 2022 15:06:34 GMT
diff --git a/packages/react/bundle-size/__css.fixture.js b/packages/react/bundle-size/__css.fixture.js
new file mode 100644
index 0000000..9a03c76
--- /dev/null
+++ b/packages/react/bundle-size/__css.fixture.js
@@ -0,0 +1,7 @@
+import { __css, mergeClasses } from '@griffel/react';
+
+console.log(__css, mergeClasses);
+
+export default {
+  name: '__css + mergeClasses (build time)',
+};
diff --git a/packages/react/bundle-size/makeStyles.fixture.js b/packages/react/bundle-size/__styles.fixture.js
similarity index 69%
rename from packages/react/bundle-size/makeStyles.fixture.js
rename to packages/react/bundle-size/__styles.fixture.js
index 92d5b23..ca7a2a8 100644
--- a/packages/react/bundle-size/makeStyles.fixture.js
+++ b/packages/react/bundle-size/__styles.fixture.js
@@ -3,5 +3,5 @@ import { __styles, mergeClasses } from '@griffel/react';
 console.log(__styles, mergeClasses);
 
 export default {
-  name: 'makeStyles + mergeClasses (build time)',
+  name: '__styles + mergeClasses (build time)',
 };
diff --git a/packages/react/package.json b/packages/react/package.json
index a80d2e5..97ce390 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/react",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "description": "React implementation of Atomic CSS-in-JS",
   "license": "MIT",
   "repository": {
@@ -9,7 +9,7 @@
   },
   "sideEffects": false,
   "dependencies": {
-    "@griffel/core": "^1.3.1",
+    "@griffel/core": "^1.4.0",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
diff --git a/packages/react/src/__css.ts b/packages/react/src/__css.ts
new file mode 100644
index 0000000..0cecbae
--- /dev/null
+++ b/packages/react/src/__css.ts
@@ -0,0 +1,20 @@
+import { __css as vanillaCSS } from '@griffel/core';
+import type { CSSClassesMapBySlot } from '@griffel/core';
+
+import { useTextDirection } from './TextDirectionContext';
+
+/**
+ * A version of makeStyles() that accepts build output as an input and skips all runtime transforms & DOM insertion.
+ *
+ * @internal
+ */
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export function __css<Slots extends string>(classesMapBySlot: CSSClassesMapBySlot<Slots>) {
+  const getStyles = vanillaCSS(classesMapBySlot);
+
+  return function useClasses(): Record<Slots, string> {
+    const dir = useTextDirection();
+
+    return getStyles({ dir });
+  };
+}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 1978932..e3c9de2 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -9,4 +9,5 @@ export { RendererProvider, useRenderer as useRenderer_unstable } from './Rendere
 export { TextDirectionProvider } from './TextDirectionContext';
 
 // Private exports, are used by build time transforms
+export { __css } from './__css';
 export { __styles } from './__styles';
diff --git a/packages/react/src/renderToStyleElements.ts b/packages/react/src/renderToStyleElements.ts
index 944f5bb..02ffe48 100644
--- a/packages/react/src/renderToStyleElements.ts
+++ b/packages/react/src/renderToStyleElements.ts
@@ -8,9 +8,26 @@ import type { GriffelRenderer } from '@griffel/core';
  * @public
  */
 export function renderToStyleElements(renderer: GriffelRenderer): React.ReactElement[] {
-  const stylesheets = Object.values(renderer.stylesheets).sort((a, b) => {
-    return styleBucketOrdering.indexOf(a.bucketName) - styleBucketOrdering.indexOf(b.bucketName);
-  });
+  const stylesheets = Object.values(renderer.stylesheets)
+    // first sort: bucket names
+    .sort((a, b) => {
+      return styleBucketOrdering.indexOf(a.bucketName) - styleBucketOrdering.indexOf(b.bucketName);
+    })
+    // second sort: media queries
+    .sort((a, b) => {
+      const mediaA = a.elementAttributes['media'];
+      const mediaB = b.elementAttributes['media'];
+
+      if (mediaA && mediaB) {
+        return renderer.compareMediaQueries(mediaA, mediaB);
+      }
+
+      if (mediaA || mediaB) {
+        return mediaA ? 1 : -1;
+      }
+
+      return 0;
+    });
 
   return stylesheets
     .map(stylesheet => {
diff --git a/packages/webpack-extraction-plugin/CHANGELOG.json b/packages/webpack-extraction-plugin/CHANGELOG.json
new file mode 100644
index 0000000..476cc9e
--- /dev/null
+++ b/packages/webpack-extraction-plugin/CHANGELOG.json
@@ -0,0 +1,20 @@
+{
+  "name": "@griffel/webpack-extraction-plugin",
+  "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/webpack-extraction-plugin_v0.0.1",
+      "version": "0.0.1",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-extraction-plugin",
+            "comment": "Bump @griffel/core to v1.4.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/packages/webpack-extraction-plugin/CHANGELOG.md b/packages/webpack-extraction-plugin/CHANGELOG.md
new file mode 100644
index 0000000..b551c74
--- /dev/null
+++ b/packages/webpack-extraction-plugin/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log - @griffel/webpack-extraction-plugin
+
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
+
+<!-- Start content -->
+
+## 0.0.1
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.0
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/output.css
new file mode 100644
index 0000000..807e034
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/output.css
@@ -0,0 +1,12 @@
+.fe3e8s9 {
+  color: red;
+}
+.fycuoez {
+  padding-left: 4px;
+}
+.f8wuabp {
+  padding-right: 4px;
+}
+.fcnqdeg {
+  background-color: green;
+}
diff --git a/packages/webpack-extraction-plugin/package.json b/packages/webpack-extraction-plugin/package.json
index 6844523..0877c8e 100644
--- a/packages/webpack-extraction-plugin/package.json
+++ b/packages/webpack-extraction-plugin/package.json
@@ -16,7 +16,7 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.3.1",
+    "@griffel/core": "^1.4.0",
     "webpack": "^5"
   }
 }
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
new file mode 100644
index 0000000..2c83383
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
@@ -0,0 +1,60 @@
+import { Compilation } from 'webpack';
+import type { Compiler, sources } from 'webpack';
+
+/**
+ * Forces all files with `griffel.css` be concatenated into a single asset.
+ */
+function forceCSSIntoOneStyleSheet(compiler: Compiler) {
+  compiler.options.optimization ??= {};
+  compiler.options.optimization.splitChunks ??= {};
+  compiler.options.optimization.splitChunks.cacheGroups ??= {};
+
+  compiler.options.optimization.splitChunks.cacheGroups['griffel'] = {
+    name: `griffel`,
+    type: 'css/mini-extract',
+    chunks: 'all',
+    test: /griffel.css/,
+    enforce: true,
+  };
+}
+
+function getAssetSourceContents(assetSource: sources.Source): string {
+  const source = assetSource.source();
+
+  if (typeof source === 'string') {
+    return source;
+  }
+
+  return source.toString();
+}
+
+export class GriffelCSSExtractionPlugin {
+  static loader = require.resolve('./webpackLoader');
+
+  apply(compiler: Compiler): void {
+    forceCSSIntoOneStyleSheet(compiler);
+
+    compiler.hooks.compilation.tap('GriffelExtractPlugin', compilation => {
+      compilation.hooks.processAssets.tap(
+        {
+          name: 'GriffelExtractPlugin',
+          stage: Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS,
+        },
+        assets => {
+          const griffelAsset = assets['griffel.css'];
+
+          if (!griffelAsset) {
+            return;
+          }
+
+          // TODO: implement sorting for buckets and media queries
+
+          compilation.updateAsset(
+            'griffel.css',
+            new compiler.webpack.sources.RawSource(getAssetSourceContents(griffelAsset)),
+          );
+        },
+      );
+    });
+  }
+}
diff --git a/packages/webpack-extraction-plugin/src/index.ts b/packages/webpack-extraction-plugin/src/index.ts
index cb0ff5c..6e1e6f2 100644
--- a/packages/webpack-extraction-plugin/src/index.ts
+++ b/packages/webpack-extraction-plugin/src/index.ts
@@ -1,1 +1,1 @@
-export {};
+export { GriffelCSSExtractionPlugin } from './GriffelCSSExtractionPlugin';
diff --git a/packages/webpack-extraction-plugin/tsconfig.json b/packages/webpack-extraction-plugin/tsconfig.json
index 25cc731..0639863 100644
--- a/packages/webpack-extraction-plugin/tsconfig.json
+++ b/packages/webpack-extraction-plugin/tsconfig.json
@@ -3,7 +3,7 @@
   "compilerOptions": {
     "allowJs": true,
     "checkJs": true,
-    "lib": ["es2019", "dom"],
+    "lib": ["es2019", "dom", "DOM.Iterable"],
     "forceConsistentCasingInFileNames": true,
     "strict": true,
     "noImplicitOverride": true,
diff --git a/packages/webpack-loader/CHANGELOG.json b/packages/webpack-loader/CHANGELOG.json
index efcc416..c041bf1 100644
--- a/packages/webpack-loader/CHANGELOG.json
+++ b/packages/webpack-loader/CHANGELOG.json
@@ -1,6 +1,27 @@
 {
   "name": "@griffel/webpack-loader",
   "entries": [
+    {
+      "date": "Thu, 23 Jun 2022 08:37:05 GMT",
+      "tag": "@griffel/webpack-loader_v2.0.7",
+      "version": "2.0.7",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/babel-preset to v1.3.2",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/react to v1.2.0",
+            "commit": "6b98eb67f7b4399bcc9bd66607e296640449ae98"
+          }
+        ]
+      }
+    },
     {
       "date": "Thu, 26 May 2022 15:06:34 GMT",
       "tag": "@griffel/webpack-loader_v2.0.6",
diff --git a/packages/webpack-loader/CHANGELOG.md b/packages/webpack-loader/CHANGELOG.md
index df11828..4595eb6 100644
--- a/packages/webpack-loader/CHANGELOG.md
+++ b/packages/webpack-loader/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Change Log - @griffel/webpack-loader
 
-This log was last generated on Thu, 26 May 2022 15:06:34 GMT and should not be manually modified.
+This log was last generated on Thu, 23 Jun 2022 08:37:05 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 2.0.7
+
+Thu, 23 Jun 2022 08:37:05 GMT
+
+### Patches
+
+- Bump @griffel/babel-preset to v1.3.2
+- Bump @griffel/react to v1.2.0
+
 ## 2.0.6
 
 Thu, 26 May 2022 15:06:34 GMT
diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json
index 328fd03..3326acb 100644
--- a/packages/webpack-loader/package.json
+++ b/packages/webpack-loader/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/webpack-loader",
-  "version": "2.0.6",
+  "version": "2.0.7",
   "description": "Webpack loader that performs build time transforms for Griffel",
   "license": "MIT",
   "repository": {
@@ -9,14 +9,14 @@
   },
   "dependencies": {
     "@babel/core": "^7.12.13",
-    "@griffel/babel-preset": "^1.3.1",
+    "@griffel/babel-preset": "^1.3.2",
     "enhanced-resolve": "^5.8.2",
     "loader-utils": "^2.0.0",
     "schema-utils": "^3.1.1",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/react": "^1.1.0",
+    "@griffel/react": "^1.2.0",
     "webpack": "^5"
   }
 }
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 0bfcb9c..e607fba 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -10,7 +10,7 @@
     "importHelpers": true,
     "target": "es2017",
     "module": "esnext",
-    "lib": ["es2017", "dom"],
+    "lib": ["es2017", "dom", "DOM.Iterable"],
     "skipLibCheck": true,
     "skipDefaultLibCheck": true,
     "baseUrl": ".",
diff --git a/yarn.lock b/yarn.lock
index 0b14664..2840b10 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6262,6 +6262,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/mini-css-extract-plugin@npm:2.5.1":
+  version: 2.5.1
+  resolution: "@types/mini-css-extract-plugin@npm:2.5.1"
+  dependencies:
+    mini-css-extract-plugin: "*"
+  checksum: 047a8600d49e765e793c009a03af6cb5dcf5c4aa9ee37826d5a37a46bc7d986fd236b7c5eb03089c23462b5c20ee8b97fdb9e126a82312090f33f1e2a22a505f
+  languageName: node
+  linkType: hard
+
 "@types/minimatch@npm:*, @types/minimatch@npm:^3.0.3":
   version: 3.0.5
   resolution: "@types/minimatch@npm:3.0.5"
@@ -13676,6 +13685,7 @@ __metadata:
     "@types/jest": 27.0.2
     "@types/js-beautify": ^1.13.3
     "@types/loader-utils": 2.0.3
+    "@types/mini-css-extract-plugin": 2.5.1
     "@types/node": 14.14.33
     "@types/react": 17.0.30
     "@types/react-dom": 17.0.9
@@ -13711,6 +13721,7 @@ __metadata:
     js-beautify: ^1.14.0
     loader-utils: ^2.0.0
     log-symbols: 5.1.0
+    mini-css-extract-plugin: 2.6.0
     monosize: 0.0.3
     monosize-storage-upstash: 0.0.2
     nano-staged: 0.5.0
@@ -17388,6 +17399,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"mini-css-extract-plugin@npm:*, mini-css-extract-plugin@npm:2.6.0":
+  version: 2.6.0
+  resolution: "mini-css-extract-plugin@npm:2.6.0"
+  dependencies:
+    schema-utils: ^4.0.0
+  peerDependencies:
+    webpack: ^5.0.0
+  checksum: ea73bd66558de7a37db094fe68fa130e7a725ab15f880ff8467a75d9c4c2f1576d20720088ea22af9922a94b8600bbfef0c6acaf10d12a32a9bd20a90ba3c35f
+  languageName: node
+  linkType: hard
+
 "mini-css-extract-plugin@npm:^1.6.0":
   version: 1.6.2
   resolution: "mini-css-extract-plugin@npm:1.6.2"


Test patch:
diff --git a/packages/core/src/renderer/getStyleSheetForBucket.test.ts b/packages/core/src/renderer/getStyleSheetForBucket.test.ts
index fbb4606..1876111 100644
--- a/packages/core/src/renderer/getStyleSheetForBucket.test.ts
+++ b/packages/core/src/renderer/getStyleSheetForBucket.test.ts
@@ -1,5 +1,6 @@
-import { getStyleSheetForBucket } from './getStyleSheetForBucket';
+import { getStyleSheetForBucket, styleBucketOrdering } from './getStyleSheetForBucket';
 import { createDOMRenderer } from './createDOMRenderer';
+import { DATA_BUCKET_ATTR } from '../constants';
 
 function createFakeDocument(): Document {
   const doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
@@ -9,15 +10,86 @@ function createFakeDocument(): Document {
 }
 
 describe('getStyleSheetForBucket', () => {
-  it('sets "data-make-styles-bucket" attribute', () => {
+  it('sets "data-make-styles-bucket" attribute in order', () => {
     const target = createFakeDocument();
     const renderer = createDOMRenderer();
 
+    getStyleSheetForBucket('l', target, renderer);
     getStyleSheetForBucket('d', target, renderer);
+    getStyleSheetForBucket('v', target, renderer);
+    getStyleSheetForBucket('a', target, renderer);
+    getStyleSheetForBucket('i', target, renderer);
     getStyleSheetForBucket('h', target, renderer);
+    getStyleSheetForBucket('m', target, renderer);
+    getStyleSheetForBucket('w', target, renderer);
+    getStyleSheetForBucket('t', target, renderer);
+    getStyleSheetForBucket('k', target, renderer);
+    getStyleSheetForBucket('f', target, renderer);
 
-    expect(target.head.innerHTML).toMatchInlineSnapshot(
-      `"<style xmlns=\\"http://www.w3.org/1999/xhtml\\" data-make-styles-bucket=\\"d\\"></style><style xmlns=\\"http://www.w3.org/1999/xhtml\\" data-make-styles-bucket=\\"h\\"></style>"`,
+    const styleElements = target.head.querySelectorAll(`[${DATA_BUCKET_ATTR}]`);
+    const styleElementOrder = Array.from(styleElements).map(styleElement =>
+      styleElement.getAttribute(DATA_BUCKET_ATTR),
     );
+    expect(styleElementOrder).toEqual(styleBucketOrdering);
+  });
+
+  it('sets "data-make-styles-bucket" attribute in order with media queries', () => {
+    const target = createFakeDocument();
+    const mediaQueryOrder = ['(max-width: 1px)', '(max-width: 2px)', '(max-width: 3px)', '(max-width: 4px)'];
+    const renderer = createDOMRenderer(target, {
+      compareMediaQueries(a, b) {
+        return mediaQueryOrder.indexOf(a) - mediaQueryOrder.indexOf(b);
+      },
+    });
+
+    getStyleSheetForBucket('l', target, renderer);
+    getStyleSheetForBucket('d', target, renderer);
+    getStyleSheetForBucket('v', target, renderer);
+
+    getStyleSheetForBucket('m', target, renderer, {}, { m: '(max-width: 3px)' });
+
+    getStyleSheetForBucket('a', target, renderer);
+    getStyleSheetForBucket('i', target, renderer);
+
+    getStyleSheetForBucket('m', target, renderer, {}, { m: '(max-width: 1px)' });
+
+    getStyleSheetForBucket('h', target, renderer);
+
+    getStyleSheetForBucket('m', target, renderer, {}, { m: '(max-width: 4px)' });
+
+    getStyleSheetForBucket('w', target, renderer);
+    getStyleSheetForBucket('t', target, renderer);
+    getStyleSheetForBucket('k', target, renderer);
+    getStyleSheetForBucket('f', target, renderer);
+
+    getStyleSheetForBucket('m', target, renderer, {}, { m: '(max-width: 2px)' });
+
+    const styleElements = target.head.querySelectorAll(`[${DATA_BUCKET_ATTR}]`);
+    const styleElementOrder = Array.from(styleElements).map(styleElement =>
+      styleElement.getAttribute(DATA_BUCKET_ATTR),
+    );
+    expect(styleElementOrder).toMatchInlineSnapshot(`
+      Array [
+        "d",
+        "l",
+        "v",
+        "w",
+        "f",
+        "i",
+        "h",
+        "a",
+        "k",
+        "t",
+        "m",
+        "m",
+        "m",
+        "m",
+      ]
+    `);
+
+    const actualMediaQueryOrder = Array.from(styleElements)
+      .map(styleElement => styleElement.getAttribute('media'))
+      .filter(Boolean);
+    expect(actualMediaQueryOrder).toEqual(mediaQueryOrder);
   });
 });
diff --git a/packages/react/src/renderToStyleElements-node.test.tsx b/packages/react/src/renderToStyleElements-node.test.tsx
index 354e002..3a6d875 100644
--- a/packages/react/src/renderToStyleElements-node.test.tsx
+++ b/packages/react/src/renderToStyleElements-node.test.tsx
@@ -116,6 +116,101 @@ describe('renderToStyleElements', () => {
     `);
   });
 
+  it('handles media query order', () => {
+    const useExampleStyles = makeStyles({
+      media: {
+        color: 'red',
+        '@media (max-width: 4px)': {
+          ':hover': { color: 'blue' },
+        },
+        '@media (max-width: 2px)': {
+          ':hover': { color: 'blue' },
+        },
+        '@supports (display: grid)': {
+          color: 'green',
+        },
+        '@media (max-width: 3px)': {
+          ':hover': { color: 'blue' },
+        },
+        '@media (max-width: 1px)': {
+          ':hover': { color: 'blue' },
+        },
+      },
+    });
+    const ExampleComponent: React.FC = () => {
+      const classes = useExampleStyles();
+
+      return <div className={classes.media} />;
+    };
+
+    const mediaQueryOrder = ['(max-width: 1px)', '(max-width: 2px)', '(max-width: 3px)', '(max-width: 4px)'];
+    const renderer = createDOMRenderer(undefined, {
+      compareMediaQueries(a, b) {
+        return mediaQueryOrder.indexOf(a) - mediaQueryOrder.indexOf(b);
+      },
+    });
+
+    ReactDOM.renderToStaticMarkup(
+      <RendererProvider renderer={renderer}>
+        <ExampleComponent />
+      </RendererProvider>,
+    );
+
+    expect(ReactDOM.renderToStaticMarkup(<>{renderToStyleElements(renderer)}</>)).toMatchInlineSnapshot(`
+      <style data-make-styles-bucket="d" data-make-styles-rehydration="true">
+        .fe3e8s9 {
+          color: red;
+        }</style
+      ><style data-make-styles-bucket="t" data-make-styles-rehydration="true">
+        @supports (display: grid) {
+          .f1vq01kz {
+            color: green;
+          }
+        }</style
+      ><style
+        media="(max-width: 1px)"
+        data-make-styles-bucket="m"
+        data-make-styles-rehydration="true"
+      >
+        @media (max-width: 1px) {
+          .f1f7njb2:hover {
+            color: blue;
+          }
+        }</style
+      ><style
+        media="(max-width: 2px)"
+        data-make-styles-bucket="m"
+        data-make-styles-rehydration="true"
+      >
+        @media (max-width: 2px) {
+          .f1c6999y:hover {
+            color: blue;
+          }
+        }</style
+      ><style
+        media="(max-width: 3px)"
+        data-make-styles-bucket="m"
+        data-make-styles-rehydration="true"
+      >
+        @media (max-width: 3px) {
+          .f1qdcc3n:hover {
+            color: blue;
+          }
+        }</style
+      ><style
+        media="(max-width: 4px)"
+        data-make-styles-bucket="m"
+        data-make-styles-rehydration="true"
+      >
+        @media (max-width: 4px) {
+          .f1b4up97:hover {
+            color: blue;
+          }
+        }
+      </style>
+    `);
+  });
+
   it('handles keyframes', () => {
     const useExampleStyles = makeStyles({
       keyframe: {
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
new file mode 100644
index 0000000..af2d145
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
@@ -0,0 +1,199 @@
+import * as fs from 'fs';
+import { createFsFromVolume, Volume } from 'memfs';
+import * as MiniCssExtractPlugin from 'mini-css-extract-plugin';
+import * as path from 'path';
+import * as prettier from 'prettier';
+import * as webpack from 'webpack';
+import { merge } from 'webpack-merge';
+
+import { GriffelCSSExtractionPlugin } from './GriffelCSSExtractionPlugin';
+
+type CompileOptions = {
+  webpackConfig?: webpack.Configuration;
+};
+
+const prettierConfig = JSON.parse(
+  fs.readFileSync(path.resolve(__dirname, '../../../.prettierrc'), { encoding: 'utf-8' }),
+);
+
+async function compileSourceWithWebpack(
+  entryPath: string,
+  options: CompileOptions,
+): Promise<{
+  filesList: string[];
+  cssOutput: string;
+}> {
+  const defaultConfig: webpack.Configuration = {
+    context: __dirname,
+    entry: entryPath,
+
+    mode: 'development',
+
+    output: {
+      path: path.resolve(__dirname),
+      filename: 'bundle.js',
+      pathinfo: false,
+    },
+
+    module: {
+      rules: [
+        {
+          test: /\.(ts|tsx)$/,
+          include: path.dirname(entryPath),
+          use: {
+            loader: GriffelCSSExtractionPlugin.loader,
+          },
+        },
+        {
+          test: /\.css$/,
+          use: [MiniCssExtractPlugin.loader, 'css-loader'],
+        },
+      ],
+    },
+    plugins: [new GriffelCSSExtractionPlugin(), new MiniCssExtractPlugin()],
+
+    resolve: {
+      alias: {
+        '@griffel/core': path.resolve(__dirname, '..', '..', '..', 'dist', 'packages', 'react', 'index.esm.js'),
+        '@griffel/react': path.resolve(__dirname, '..', '..', '..', 'dist', 'packages', 'react', 'index.esm.js'),
+      },
+      extensions: ['.js', '.ts'],
+    },
+  };
+
+  const webpackConfig = merge(defaultConfig, options.webpackConfig || {});
+  const compiler = webpack(webpackConfig);
+
+  const virtualFsVolume = createFsFromVolume(new Volume());
+
+  compiler.outputFileSystem = virtualFsVolume;
+  compiler.outputFileSystem.join = path.join.bind(path);
+
+  return new Promise((resolve, reject) => {
+    compiler.run((err, stats) => {
+      if (err) {
+        reject(err);
+        return;
+      }
+
+      if (typeof stats === 'undefined') {
+        reject(new Error('"stats" from Webpack are not available, unknown error...'));
+        return;
+      }
+
+      if (stats.hasErrors()) {
+        reject(stats.toJson().errors![0]);
+        return;
+      }
+
+      const cssFilePath = path.resolve(__dirname, 'griffel.css');
+
+      if (!virtualFsVolume.existsSync(cssFilePath)) {
+        reject(new Error('"griffel.css" does not exist in filesystem'));
+      }
+
+      const cssOutput = virtualFsVolume.readFileSync(cssFilePath, {
+        encoding: 'utf-8',
+      }) as string;
+      const filesList = virtualFsVolume.readdirSync(__dirname) as string[];
+
+      resolve({
+        cssOutput,
+        filesList,
+      });
+    });
+  });
+}
+
+function fixLineEndings(value: string) {
+  return String(value).replace(/\r?\n/g, '\n').trim();
+}
+
+/**
+ * Test utility similar to "babel-plugin-tester".
+ *
+ * See https://webpack.js.org/contribute/writing-a-loader/#testing.
+ */
+function testFixture(fixtureName: string, options: CompileOptions = {}) {
+  it(`"${fixtureName}" fixture`, async () => {
+    const fixturePath = path.resolve(__dirname, '..', '__fixtures__', 'webpack', fixtureName);
+
+    const tsCodePath = path.resolve(fixturePath, 'code.ts');
+    const tsxCodePath = path.resolve(fixturePath, 'code.tsx');
+
+    const inputPath = [fs.existsSync(tsCodePath) && tsCodePath, fs.existsSync(tsxCodePath) && tsxCodePath].find(
+      Boolean,
+    );
+
+    const errorPath = path.resolve(fixturePath, 'error.ts');
+    const expectedError = fs.existsSync(errorPath) && require(errorPath);
+
+    const fsSnapshotPath = path.resolve(fixturePath, 'fs.json');
+    const cssOutputPath = path.resolve(fixturePath, 'output.css');
+
+    if (!inputPath) {
+      throw new Error(`Failed to find "code.{js,ts,tsx}" in "${fixturePath}"`);
+    }
+
+    if (!fs.existsSync(fsSnapshotPath)) {
+      throw new Error(`Failed to find "fs.json" in "${fixturePath}"`);
+    }
+
+    if (!fs.existsSync(cssOutputPath)) {
+      throw new Error(`Failed to find "output.css" in "${fixturePath}"`);
+    }
+
+    if (!cssOutputPath && !expectedError) {
+      throw new Error(`Failed to find "output.css" or "error.ts" in "${fixturePath}"`);
+    }
+
+    if (expectedError) {
+      if (!expectedError.default) {
+        throw new Error(
+          `Please check that "error.ts" contains a default export with an error or regex in "${fixturePath}"`,
+        );
+      }
+    }
+
+    let resultCSS = '';
+    let resultFsSnapshot: string[] = [];
+
+    let resultError: Error | webpack.StatsError = new Error();
+
+    try {
+      const result = await compileSourceWithWebpack(inputPath, options);
+
+      resultCSS = fixLineEndings(
+        prettier.format(result.cssOutput, {
+          ...prettierConfig,
+          parser: 'css',
+        }),
+      );
+      resultFsSnapshot = result.filesList;
+    } catch (err) {
+      if (expectedError) {
+        resultError = err as webpack.StatsError;
+      } else {
+        throw err;
+      }
+    }
+
+    if (cssOutputPath) {
+      const cssOutput = fixLineEndings(await fs.promises.readFile(cssOutputPath, { encoding: 'utf8' }));
+      const fsSnapshot = JSON.parse(await fs.promises.readFile(fsSnapshotPath, { encoding: 'utf8' }));
+
+      expect(resultFsSnapshot).toMatchObject(fsSnapshot);
+      expect(resultCSS).toBe(cssOutput);
+      return;
+    }
+
+    if (expectedError) {
+      expect(resultError.message).toMatch(expectedError.default);
+    }
+  }, 15000);
+}
+
+describe('webpackLoader', () => {
+  // Basic assertions
+  testFixture('basic-rules');
+});