Repository: microsoft/griffel
Pull request #145 (2022-06-21 10:19:39)

chore: add __css() functions for upcoming CSS extraction
This PR adds new `__css` functions to `@griffel/core` & `@griffel/react`.

The difference to existing `__styles` is that the part related to CSS insertion is completely stripped.

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/6865b1ccce9c0ac86da055663d3a20a331986c1d'>6865b1ccce9c0ac86da055663d3a20a331986c1d</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-4ea31e7f-39dc-486b-8a22-3545f609a5cb.json b/change/@griffel-core-4ea31e7f-39dc-486b-8a22-3545f609a5cb.json
new file mode 100644
index 0000000..0ba3d2b
--- /dev/null
+++ b/change/@griffel-core-4ea31e7f-39dc-486b-8a22-3545f609a5cb.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "chore: add __css() functions for upcoming CSS extraction",
+  "packageName": "@griffel/core",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-7e9b9cb5-ecc6-40d6-8a4e-5d3a2d0e93e6.json b/change/@griffel-core-7e9b9cb5-ecc6-40d6-8a4e-5d3a2d0e93e6.json
new file mode 100644
index 0000000..d9d0a25
--- /dev/null
+++ b/change/@griffel-core-7e9b9cb5-ecc6-40d6-8a4e-5d3a2d0e93e6.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: Add `compareMediaQueries` option to DOM renderer",
+  "packageName": "@griffel/core",
+  "email": "lingfangao@hotmail.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-react-8818a204-4697-4df6-8848-771f49c2bfdb.json b/change/@griffel-react-8818a204-4697-4df6-8848-771f49c2bfdb.json
new file mode 100644
index 0000000..d4f4dbd
--- /dev/null
+++ b/change/@griffel-react-8818a204-4697-4df6-8848-771f49c2bfdb.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "chore: add __css() functions for upcoming CSS extraction",
+  "packageName": "@griffel/react",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-react-fdb5ef2b-a58e-4c22-95af-6769c901b131.json b/change/@griffel-react-fdb5ef2b-a58e-4c22-95af-6769c901b131.json
new file mode 100644
index 0000000..deb0ef8
--- /dev/null
+++ b/change/@griffel-react-fdb5ef2b-a58e-4c22-95af-6769c901b131.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: Update style rehydration to sort media queries",
+  "packageName": "@griffel/react",
+  "email": "lingfangao@hotmail.com",
+  "dependentChangeType": "patch"
+}
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/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/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/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/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/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": ".",


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: {