Repository: microsoft/griffel
Pull request #130 (2022-06-04 01:47:24)

Added shorthand for grid-area
## Changes
- Added gridArea function with overloads for 1-4 values (row start, column start, row end, column end)
- Created logic based on grid-area MDN for missing values
- Added local helper function to determine if value is a custom-ident (could be pulled into a utils if other shorthands need it)
- Added unit tests for each overload

## Open issues
- The ValueOrArray generic did not see appropriate for the gridArea function, so I defined GridAreaInput as CSS.Property.GridArea.  I'm unsure if both value and arrays should be supported for gridArea.

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.269 kB`<br />`7.549 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStaticStyles-runtime.fixture.js'>makeStaticStyles (runtime)</abbr>    |  `8.247 kB`<br />`3.595 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles-runtime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr> |  `21.294 kB`<br />`7.98 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles.fixture.js'>makeStyles + mergeClasses (build time)</abbr>      |  `2.811 kB`<br />`1.275 kB` |

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

<!-- Sticky Pull Request Comment -->
miroslavstastny: > 3. check there is a CSS variable passed to the shorthand function and throw in that case (is it a valid usecase to pass a theme token to `grid-area`? If so, isn't it just an edge case?)

`flex` shorthand has been merged recently in #127 which silently fails for `flex('var(--my-var)')`.
@layershifter, @GeoffCoxMSFT  I vote for `grid-area` to fail similarly, what do you think?
miroslavstastny: Discussed offline with @layershifter and @ling1726 - let's check for the CSS variable in the value and fail silently.
Either we can check for `--` in the string or `var(`?
ling1726: > Discussed offline with @layershifter and @ling1726 - let's check for the CSS variable in the value and fail silently. Either we can check for `--` in the string or `var(`?

https://github.com/kentcdodds/rtl-css-js/pull/62/files#diff-be37810fd676724e7edb9627d74b7583e7c38153b3804c2213e84ae32cf4a65fR14

That should work, it's in rtl-css-js and we've been using it internally for some time
GeoffCoxMSFT: > > 3. check there is a CSS variable passed to the shorthand function and throw in that case (is it a valid usecase to pass a theme token to `grid-area`? If so, isn't it just an edge case?)
> 
> `flex` shorthand has been merged recently in #127 which silently fails for `flex('var(--my-var)')`. @layershifter, @GeoffCoxMSFT I vote for `grid-area` to fail similarly, what do you think?

I update the PR for the fail silently approach.  
I also added a console.error suppression for the flex shorthand tests to avoid failing tests when running `yarn test` locally.

I created this codepen to see how css vars are supported. https://codepen.io/GeoffCox/pen/xxYQpdG
We could have a 6th option: Check for css var and apply it to grid-row-start etc.


Patch:
diff --git a/README.md b/README.md
index f6f7e6b..283c9b0 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,14 @@ Griffel ([_German for stylus/pen_](https://en.wiktionary.org/wiki/Griffel)) is C
 
 🚀 &nbsp; Zero config start: Griffel has both runtime & build time implementations
 
-🔥 &nbsp; Optional build time transforms to improve performance
+🔥 &nbsp; Optional [build time transforms](./packages/webpack-loader) to improve performance
 
 💪 &nbsp; Type-safe styles via [csstype](https://github.com/frenic/csstype)
 
 🧩 &nbsp; Uses Atomic CSS to reuse styles and avoid specificity issues with CSS
 
+📝 &nbsp; _Experimental_ CSS extraction with [Webpack plugin](./packages/webpack-extraction-plugin)
+
 ### Griffel with React
 
 ```bash
diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md
new file mode 100644
index 0000000..5fe1a72
--- /dev/null
+++ b/apps/benchmark/README.md
@@ -0,0 +1,17 @@
+# Performance benchmark
+
+This app runs a performance benchmark of different css styling implementations courtesy of [react-native-web](https://github.com/necolas/react-native-web/tree/master/packages/benchmarks).
+
+## Usage
+
+To start benchmarking run these commands from the root of the repo:
+
+```shell
+# Builds the app and watches for changes
+yarn nx run @griffel/benchmark:watch
+```
+
+```shell
+# Serves the benchmark app to use in the browser
+yarn nx run @griffel/benchmark:serve
+```
diff --git a/apps/benchmark/build.js b/apps/benchmark/build.js
new file mode 100644
index 0000000..13d2ab7
--- /dev/null
+++ b/apps/benchmark/build.js
@@ -0,0 +1,40 @@
+const esbuild = require('esbuild');
+const ImportGlobPlugin = require('esbuild-plugin-import-glob').default;
+const fs = require('fs');
+const path = require('path');
+const yargs = require('yargs');
+
+const argv = yargs(process.argv)
+  .options({
+    watch: { type: 'boolean', default: false },
+  })
+  .parseSync();
+
+const outDir = path.resolve(__dirname, '../../dist/apps/benchmark/');
+
+esbuild
+  .build({
+    entryPoints: ['./src/index.tsx'],
+    outfile: path.join(outDir, 'bundle.js'),
+    minify: true,
+    bundle: true,
+    plugins: [ImportGlobPlugin()],
+    ...(argv.watch && {
+      watch: {
+        onRebuild(error, result) {
+          if (error) {
+            console.error('watch build failed:', error);
+          } else {
+            console.log('watch build succeeded:', result);
+          }
+        },
+      },
+    }),
+  })
+  .then(() => {
+    if (argv.watch) {
+      console.log('watching');
+    }
+    fs.copyFileSync(path.resolve(__dirname, './index.html'), path.join(outDir, 'index.html'));
+  })
+  .catch(() => process.exit(1));
diff --git a/apps/benchmark/index.html b/apps/benchmark/index.html
new file mode 100644
index 0000000..ae1dd71
--- /dev/null
+++ b/apps/benchmark/index.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <title>Performance tests</title>
+    <meta name="viewport" content="width=device-width" />
+    <link rel="stylesheet" type="text/css" href="./bundle.css" />
+    <style>
+      html,
+      body {
+        height: 100%;
+        width: 100%;
+        overflow: hidden;
+      }
+      #root {
+        height: 100%;
+        overflow: hidden;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script src="./bundle.js"></script>
+  </body>
+</html>
diff --git a/apps/benchmark/package.json b/apps/benchmark/package.json
new file mode 100644
index 0000000..84888b7
--- /dev/null
+++ b/apps/benchmark/package.json
@@ -0,0 +1,6 @@
+{
+  "name": "@griffel/benchmark",
+  "description": "Performance benchmarking app for Griffel",
+  "private": true,
+  "dependencies": {}
+}
diff --git a/apps/benchmark/project.json b/apps/benchmark/project.json
new file mode 100644
index 0000000..09983c9
--- /dev/null
+++ b/apps/benchmark/project.json
@@ -0,0 +1,39 @@
+{
+  "root": "apps/benchmark",
+  "sourceRoot": "apps/benchmark/src",
+  "projectType": "application",
+  "targets": {
+    "build": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "apps/benchmark",
+        "commands": [{ "command": "node ./build.js" }],
+        "outputPath": ["dist/apps/benchmark"]
+      }
+    },
+    "watch": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "apps/benchmark",
+        "commands": [{ "command": "node ./build.js --watch" }],
+        "outputPath": ["dist/apps/benchmark"]
+      }
+    },
+    "serve": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "apps/benchmark",
+        "commands": [{ "command": "npx http-serve ../../dist/apps/benchmark" }]
+      }
+    },
+    "type-check": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "apps/benchmark",
+        "commands": [{ "command": "tsc -b --pretty" }],
+        "outputPath": []
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/apps/benchmark/src/App.css b/apps/benchmark/src/App.css
new file mode 100644
index 0000000..d67dae5
--- /dev/null
+++ b/apps/benchmark/src/App.css
@@ -0,0 +1,73 @@
+.app-root {
+  display: flex;
+  height: 100%;
+}
+
+.benchmark-container {
+  flex-grow: 1;
+  flex-shrink: 1;
+  flex-basis: 0%;
+}
+
+.benchmark-actions {
+  display: flex;
+}
+
+.benchmark-action {
+  min-height: 40px;
+  font-size: 20px;
+  cursor: pointer;
+  flex-grow: 1;
+}
+
+.results-list {
+  margin-top: 10px;
+  border-top: 1px solid black;
+  padding: 10px 0;
+}
+
+.render-container {
+  background-color: black;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-grow: 1;
+  flex-shrink: 1;
+  flex-basis: 0%;
+}
+
+.report-card {
+  margin: 10px 0;
+  padding: 4px 10px;
+  border-bottom: 1px solid gray;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.report-card .main {
+  font-size: 20px;
+  font-weight: 700;
+}
+
+.report-card .column {
+ display: flex;
+ flex-direction: column;
+}
+
+.select {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.select select {
+  cursor: pointer;
+  background-color: transparent;
+  padding: 0 1em;
+  border-radius: 4px;
+  box-shadow: none;
+  box-sizing: border-box;
+  font-size: 20px;
+}
+
diff --git a/apps/benchmark/src/App.tsx b/apps/benchmark/src/App.tsx
new file mode 100644
index 0000000..a1d192d
--- /dev/null
+++ b/apps/benchmark/src/App.tsx
@@ -0,0 +1,155 @@
+import React from 'react';
+import implementations from './implementations';
+import { ReportCard, Select } from './components';
+import tests from './tests';
+import { Benchmark } from './Benchmark';
+import type { BenchDisplayResults, BenchmarkName, BenchResultsType, LibraryName } from './types';
+
+type AppAction =
+  | { type: 'SELECT_BENCHMARK'; payload: BenchmarkName }
+  | { type: 'SELECT_LIBRARY'; payload: { library: LibraryName; version?: string } }
+  | { type: 'BENCHMARK_COMPLETED'; payload: BenchResultsType }
+  | { type: 'CLEAR_RESULTS' }
+  | { type: 'START_BENCHMARK' };
+
+interface AppState {
+  benchmark: BenchmarkName;
+  library: LibraryName;
+  version?: string;
+  results: BenchDisplayResults[];
+  inProgress: boolean;
+}
+
+const appInitialState: AppState = {
+  benchmark: 'Mount deep tree',
+  library: 'griffel',
+  results: [],
+  inProgress: false,
+};
+
+function appReducer(state: AppState, action: AppAction): AppState {
+  switch (action.type) {
+    case 'SELECT_LIBRARY':
+      return {
+        ...state,
+        library: action.payload.library,
+        version: action.payload.version,
+      };
+    case 'SELECT_BENCHMARK':
+      return {
+        ...state,
+        benchmark: action.payload,
+      };
+    case 'CLEAR_RESULTS':
+      return {
+        ...state,
+        results: [],
+      };
+    case 'BENCHMARK_COMPLETED':
+      const resultsWithCompletedBenchmark = [...state.results].slice(0, -1);
+      resultsWithCompletedBenchmark.push({
+        analysis: { ...action.payload },
+        library: state.library,
+        benchmark: state.benchmark,
+      });
+      return {
+        ...state,
+        inProgress: false,
+        results: resultsWithCompletedBenchmark,
+      };
+
+    case 'START_BENCHMARK':
+      const resultsWithNewBenchmark = [...state.results];
+      resultsWithNewBenchmark.push({
+        version: state.version,
+        library: state.library,
+        benchmark: state.benchmark,
+      });
+      return { ...state, inProgress: true, results: resultsWithNewBenchmark };
+  }
+}
+
+export const App: React.FC = () => {
+  const [state, dispatch] = React.useReducer(appReducer, appInitialState);
+  const { library, benchmark, results, inProgress } = state;
+  const imperativeRef = React.useRef({ start: () => null });
+  React.useEffect(() => {
+    if (inProgress) {
+      imperativeRef.current.start();
+    }
+  }, [inProgress]);
+
+  const benchmarkToRun = tests[benchmark][library];
+  const {
+    components: { Provider },
+  } = implementations[library];
+
+  return (
+    <div className="app-root">
+      <div className="benchmark-container">
+        <div className="benchmark-actions">
+          <button
+            className="benchmark-action"
+            disabled={inProgress}
+            onClick={() => {
+              dispatch({ type: 'START_BENCHMARK' });
+            }}
+          >
+            {inProgress ? 'Running...' : 'Run'}
+          </button>
+          <button className="benchmark-action" onClick={() => dispatch({ type: 'CLEAR_RESULTS' })}>
+            Clear results
+          </button>
+        </div>
+        <Select
+          label="Library"
+          value={library}
+          onChange={e =>
+            dispatch({
+              type: 'SELECT_LIBRARY',
+              payload: { library: e.target.value, version: implementations[e.target.value].version },
+            })
+          }
+        >
+          {Object.keys(implementations).map(implementation => (
+            <option value={implementation} key={implementation}>
+              {implementation}
+            </option>
+          ))}
+        </Select>
+        <Select
+          label="Benchmark"
+          value={benchmark}
+          onChange={e => dispatch({ type: 'SELECT_BENCHMARK', payload: e.target.value as BenchmarkName })}
+        >
+          {Object.keys(tests).map(test => (
+            <option value={test} key={test}>
+              {test}
+            </option>
+          ))}
+        </Select>
+        <div className="results-list">
+          {results.map(result => (
+            <ReportCard {...result} version={result.version} />
+          ))}
+        </div>
+      </div>
+      <div className="render-container">
+        <Provider>
+          {inProgress ? (
+            <Benchmark
+              forceLayout
+              // TODO benchmark code uses legacy refs that can't be typed - refactor it to be functional
+              // @ts-expect-error
+              ref={imperativeRef}
+              {...benchmarkToRun}
+              onComplete={res => dispatch({ type: 'BENCHMARK_COMPLETED', payload: res })}
+            />
+          ) : (
+            <benchmarkToRun.Component {...benchmarkToRun.getComponentProps({ cycle: 10 })} />
+          )}
+        </Provider>
+      </div>
+    </div>
+  );
+};
diff --git a/apps/benchmark/src/Benchmark/index.tsx b/apps/benchmark/src/Benchmark/index.tsx
new file mode 100644
index 0000000..04f91a2
--- /dev/null
+++ b/apps/benchmark/src/Benchmark/index.tsx
@@ -0,0 +1,249 @@
+/**
+ * Core benchmarking code is not original and
+ * taken from https://github.com/necolas/react-native-web/tree/master/packages/benchmarks
+ */
+
+import React, { Component } from 'react';
+import { getMean, getMedian, getStdDev } from './utils/math';
+import * as Timing from './utils/timing';
+
+import type {
+  BenchResultsType,
+  FullSampleTimingType,
+  SampleTimingType,
+  BenchmarkType,
+  BenchmarkParameters,
+} from '../types';
+
+export const BenchmarkTypes = {
+  MOUNT: 'mount',
+  UPDATE: 'update',
+  UNMOUNT: 'unmount',
+} as const;
+
+const shouldRender = (cycle: number, type: BenchmarkType): boolean => {
+  switch (type) {
+    // Render every odd iteration (first, third, etc)
+    // Mounts and unmounts the component
+    case BenchmarkTypes.MOUNT:
+    case BenchmarkTypes.UNMOUNT:
+      return !((cycle + 1) % 2);
+    // Render every iteration (updates previously rendered module)
+    case BenchmarkTypes.UPDATE:
+      return true;
+    default:
+      return false;
+  }
+};
+
+const shouldRecord = (cycle: number, type: BenchmarkType): boolean => {
+  switch (type) {
+    // Record every odd iteration (when mounted: first, third, etc)
+    case BenchmarkTypes.MOUNT:
+      return !((cycle + 1) % 2);
+    // Record every iteration
+    case BenchmarkTypes.UPDATE:
+      return true;
+    // Record every even iteration (when unmounted)
+    case BenchmarkTypes.UNMOUNT:
+      return !(cycle % 2);
+    default:
+      return false;
+  }
+};
+
+const isDone = (cycle: number, sampleCount: number, type: BenchmarkType): boolean => {
+  switch (type) {
+    case BenchmarkTypes.MOUNT:
+      return cycle >= sampleCount * 2 - 1;
+    case BenchmarkTypes.UPDATE:
+      return cycle >= sampleCount - 1;
+    case BenchmarkTypes.UNMOUNT:
+      return cycle >= sampleCount * 2;
+    default:
+      return true;
+  }
+};
+
+const sortNumbers = (a: number, b: number): number => a - b;
+
+interface BenchmarkProps extends BenchmarkParameters {
+  forceLayout?: boolean;
+  timeout: number;
+  onComplete: (x: BenchResultsType) => void;
+}
+
+type BenchmarkState = {
+  componentProps: object;
+  cycle: number;
+  running: boolean;
+};
+
+/**
+ * Benchmark
+ * TODO: documentation
+ */
+export class Benchmark extends Component<BenchmarkProps, BenchmarkState> {
+  _raf?: number;
+  _startTime: number;
+  _samples: Array<SampleTimingType>;
+
+  static displayName = 'Benchmark';
+
+  static defaultProps: {
+    sampleCount: number;
+    timeout: number;
+    type: BenchmarkProps['type'];
+  } = {
+    sampleCount: 50,
+    timeout: 10000, // 10 seconds
+    type: BenchmarkTypes.MOUNT,
+  };
+
+  constructor(props: BenchmarkProps, context?: object) {
+    super(props, context);
+    const cycle = 0;
+    const componentProps = props.getComponentProps({ cycle });
+    this.state = {
+      componentProps,
+      cycle,
+      running: false,
+    };
+    this._startTime = 0;
+    this._samples = [];
+  }
+
+  override componentWillReceiveProps(nextProps: BenchmarkProps) {
+    if (nextProps) {
+      this.setState(state => ({ componentProps: nextProps.getComponentProps(state.cycle) }));
+    }
+  }
+
+  override componentWillUpdate(nextProps: BenchmarkProps, nextState: BenchmarkState) {
+    if (nextState.running && !this.state.running) {
+      this._startTime = Timing.now();
+    }
+  }
+
+  override componentDidUpdate() {
+    const { forceLayout, sampleCount, timeout, type } = this.props;
+    const { cycle, running } = this.state;
+
+    if (running && shouldRecord(cycle, type)) {
+      this._samples[cycle].scriptingEnd = Timing.now();
+
+      // force style recalc that would otherwise happen before the next frame
+      if (forceLayout) {
+        this._samples[cycle].layoutStart = Timing.now();
+        if (document.body) {
+          document.body.offsetWidth;
+        }
+        this._samples[cycle].layoutEnd = Timing.now();
+      }
+    }
+
+    if (running) {
+      const now = Timing.now();
+      if (!isDone(cycle, sampleCount, type) && now - this._startTime < timeout) {
+        this._handleCycleComplete();
+      } else {
+        this._handleComplete(now);
+      }
+    }
+  }
+
+  override componentWillUnmount() {
+    if (this._raf) {
+      window.cancelAnimationFrame(this._raf);
+    }
+  }
+
+  override render() {
+    const { Component, type } = this.props;
+    const { componentProps, cycle, running } = this.state;
+    if (running && shouldRecord(cycle, type)) {
+      this._samples[cycle] = { scriptingStart: Timing.now() };
+    }
+
+    return running && shouldRender(cycle, type) ? <Component {...componentProps} /> : null;
+  }
+
+  start() {
+    this._samples = [];
+    this.setState(() => ({ running: true, cycle: 0 }));
+  }
+
+  _handleCycleComplete() {
+    const { getComponentProps, type } = this.props;
+    const { cycle } = this.state;
+
+    let componentProps: Record<string, string | number>;
+    if (getComponentProps) {
+      // Calculate the component props outside of the time recording (render)
+      // so that it doesn't skew results
+      componentProps = getComponentProps({ cycle });
+      // make sure props always change for update tests
+      if (type === BenchmarkTypes.UPDATE) {
+        componentProps['data-test'] = cycle;
+      }
+    }
+
+    this._raf = window.requestAnimationFrame(() => {
+      this.setState((state: BenchmarkState) => ({
+        cycle: state.cycle + 1,
+        componentProps,
+      }));
+    });
+  }
+
+  getSamples(): FullSampleTimingType[] {
+    return this._samples.reduce(
+      (
+        memo: FullSampleTimingType[],
+        { scriptingStart, scriptingEnd, layoutStart, layoutEnd }: SampleTimingType,
+      ): FullSampleTimingType[] => {
+        memo.push({
+          start: scriptingStart,
+          end: layoutEnd || scriptingEnd || 0,
+          scriptingStart,
+          scriptingEnd: scriptingEnd || 0,
+          layoutStart,
+          layoutEnd,
+        });
+        return memo;
+      },
+      [],
+    );
+  }
+
+  _handleComplete(endTime: number) {
+    const { onComplete } = this.props;
+    const samples = this.getSamples();
+
+    this.setState(() => ({ running: false, cycle: 0 }));
+
+    const runTime = endTime - this._startTime;
+    const sortedElapsedTimes = samples.map(({ start, end }) => end - start).sort(sortNumbers);
+    const sortedScriptingElapsedTimes = samples
+      .map(({ scriptingStart, scriptingEnd }) => scriptingEnd - scriptingStart)
+      .sort(sortNumbers);
+    const sortedLayoutElapsedTimes = samples
+      .map(({ layoutStart, layoutEnd }) => (layoutEnd || 0) - (layoutStart || 0))
+      .sort(sortNumbers);
+
+    onComplete({
+      startTime: this._startTime,
+      endTime,
+      runTime,
+      sampleCount: samples.length,
+      samples: samples,
+      max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
+      min: sortedElapsedTimes[0],
+      median: getMedian(sortedElapsedTimes),
+      mean: getMean(sortedElapsedTimes),
+      stdDev: getStdDev(sortedElapsedTimes),
+      meanLayout: getMean(sortedLayoutElapsedTimes),
+      meanScripting: getMean(sortedScriptingElapsedTimes),
+    });
+  }
+}
diff --git a/apps/benchmark/src/Benchmark/utils/math.ts b/apps/benchmark/src/Benchmark/utils/math.ts
new file mode 100644
index 0000000..0186776
--- /dev/null
+++ b/apps/benchmark/src/Benchmark/utils/math.ts
@@ -0,0 +1,26 @@
+type ValuesType = Array<number>;
+
+export const getStdDev = (values: ValuesType): number => {
+  const avg = getMean(values);
+
+  const squareDiffs = values.map((value: number) => {
+    const diff = value - avg;
+    return diff * diff;
+  });
+
+  return Math.sqrt(getMean(squareDiffs));
+};
+
+export const getMean = (values: ValuesType): number => {
+  const sum = values.reduce((sum: number, value: number) => sum + value, 0);
+  return sum / values.length;
+};
+
+export const getMedian = (values: ValuesType): number => {
+  if (values.length === 1) {
+    return values[0];
+  }
+
+  const numbers = values.sort((a: number, b: number) => a - b);
+  return (numbers[(numbers.length - 1) >> 1] + numbers[numbers.length >> 1]) / 2;
+};
diff --git a/apps/benchmark/src/Benchmark/utils/timing.ts b/apps/benchmark/src/Benchmark/utils/timing.ts
new file mode 100644
index 0000000..3b4ef50
--- /dev/null
+++ b/apps/benchmark/src/Benchmark/utils/timing.ts
@@ -0,0 +1,16 @@
+const NS_PER_MS = 1e6;
+const MS_PER_S = 1e3;
+
+// Returns a high resolution time (if possible) in milliseconds
+export function now(): number {
+  if (window && window.performance) {
+    return window.performance.now();
+  } else if (process && process.hrtime) {
+    const [seconds, nanoseconds] = process.hrtime();
+    const secInMS = seconds * MS_PER_S;
+    const nSecInMS = nanoseconds / NS_PER_MS;
+    return secInMS + nSecInMS;
+  } else {
+    return Date.now();
+  }
+}
diff --git a/apps/benchmark/src/components/ReportCard.tsx b/apps/benchmark/src/components/ReportCard.tsx
new file mode 100644
index 0000000..478f6d6
--- /dev/null
+++ b/apps/benchmark/src/components/ReportCard.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { BenchDisplayResults } from '../types';
+
+interface ReportCardProps extends Partial<BenchDisplayResults> {
+  version?: string;
+}
+
+const fmt = (time: number) => {
+  const milliSecs = Number(Math.round(time * 100) / 100);
+  const str = milliSecs.toFixed(2);
+  return 10 / milliSecs > 1 ? `0${str}` : str;
+};
+
+const ReportCard: React.FC<ReportCardProps> = ({ library, version, benchmark, analysis }) => {
+  const sampleCountText = analysis ? `(${analysis.sampleCount})` : '';
+  const versionText = version ? `@${version}` : '';
+
+  return (
+    <div className="report-card">
+      <div className="column">
+        <div className="main">{`${library}${versionText}`}</div>
+        <div>
+          {benchmark} {sampleCountText}
+        </div>
+      </div>
+      <div className="column">
+        {analysis ? (
+          <>
+            <div className="main">
+              {fmt(analysis.mean)} ±{fmt(analysis.stdDev)} ms
+            </div>
+            <div>
+              (S/L) {fmt(analysis.meanScripting)}/{fmt(analysis.meanLayout)} ms
+            </div>
+          </>
+        ) : (
+          <div className="main">In progress...</div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default ReportCard;
diff --git a/apps/benchmark/src/components/Select.tsx b/apps/benchmark/src/components/Select.tsx
new file mode 100644
index 0000000..84b1527
--- /dev/null
+++ b/apps/benchmark/src/components/Select.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+interface SelectProps {
+  label: string;
+}
+
+const Select: React.FC<SelectProps & React.SelectHTMLAttributes<HTMLSelectElement>> = props => {
+  const { children, label, ...rest } = props;
+  return (
+    <div className="select">
+      <label>{label}</label>
+      <select {...rest}>{children}</select>
+    </div>
+  );
+};
+
+export default Select;
diff --git a/apps/benchmark/src/components/index.ts b/apps/benchmark/src/components/index.ts
new file mode 100644
index 0000000..4b81c7c
--- /dev/null
+++ b/apps/benchmark/src/components/index.ts
@@ -0,0 +1,2 @@
+export { default as Select } from './Select';
+export { default as ReportCard } from './ReportCard';
diff --git a/apps/benchmark/src/implementations/emotion/Box.tsx b/apps/benchmark/src/implementations/emotion/Box.tsx
new file mode 100644
index 0000000..de466ec
--- /dev/null
+++ b/apps/benchmark/src/implementations/emotion/Box.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { BoxProps } from '../types';
+import View from './View';
+
+const Box: React.FC<BoxProps> = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
+  <View
+    {...other}
+    style={[styles[`color${color}`], fixed && styles.fixed, layout === 'row' && styles.row, outer && styles.outer]}
+  />
+);
+
+const styles = {
+  outer: {
+    alignSelf: 'flex-start',
+    padding: 4,
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  color0: {
+    backgroundColor: '#14171A',
+  },
+  color1: {
+    backgroundColor: '#AAB8C2',
+  },
+  color2: {
+    backgroundColor: '#E6ECF0',
+  },
+  color3: {
+    backgroundColor: '#FFAD1F',
+  },
+  color4: {
+    backgroundColor: '#F45D22',
+  },
+  color5: {
+    backgroundColor: '#E0245E',
+  },
+  fixed: {
+    width: 6,
+    height: 6,
+  },
+};
+
+export default Box;
diff --git a/apps/benchmark/src/implementations/emotion/Dot.tsx b/apps/benchmark/src/implementations/emotion/Dot.tsx
new file mode 100644
index 0000000..6302795
--- /dev/null
+++ b/apps/benchmark/src/implementations/emotion/Dot.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { css } from '@emotion/css';
+import { DotProps } from '../types';
+
+const Dot: React.FC<DotProps> = ({ size, x, y, children, color }) => (
+  <div
+    className={css([
+      styles.root,
+      {
+        borderBottomColor: color,
+        borderRightWidth: `${size / 2}px`,
+        borderBottomWidth: `${size / 2}px`,
+        borderLeftWidth: `${size / 2}px`,
+        marginLeft: `${x}px`,
+        marginTop: `${y}px`,
+      },
+    ])}
+  >
+    {children}
+  </div>
+);
+
+const styles = {
+  root: {
+    position: 'absolute',
+    cursor: 'pointer',
+    width: 0,
+    height: 0,
+    borderColor: 'transparent',
+    borderStyle: 'solid',
+    borderTopWidth: 0,
+    transform: 'translate(50%, 50%)',
+  },
+} as const;
+
+export default Dot;
diff --git a/apps/benchmark/src/implementations/emotion/Provider.ts b/apps/benchmark/src/implementations/emotion/Provider.ts
new file mode 100644
index 0000000..864fc43
--- /dev/null
+++ b/apps/benchmark/src/implementations/emotion/Provider.ts
@@ -0,0 +1,2 @@
+import View from './View';
+export default View;
diff --git a/apps/benchmark/src/implementations/emotion/View.tsx b/apps/benchmark/src/implementations/emotion/View.tsx
new file mode 100644
index 0000000..f01e10f
--- /dev/null
+++ b/apps/benchmark/src/implementations/emotion/View.tsx
@@ -0,0 +1,25 @@
+import { css } from '@emotion/css';
+import React from 'react';
+
+const View: React.FC<{ style: any }> = ({ style = [], ...rest }) => {
+  return <div {...rest} className={css([viewStyle, ...style])} />;
+};
+
+const viewStyle = {
+  alignItems: 'stretch',
+  borderWidth: 0,
+  borderStyle: 'solid',
+  boxSizing: 'border-box',
+  display: 'flex',
+  flexBasis: 'auto',
+  flexDirection: 'column',
+  flexShrink: 0,
+  margin: 0,
+  padding: 0,
+  position: 'relative',
+  // fix flexbox bugs
+  minHeight: 0,
+  minWidth: 0,
+};
+
+export default View;
diff --git a/apps/benchmark/src/implementations/emotion/index.ts b/apps/benchmark/src/implementations/emotion/index.ts
new file mode 100644
index 0000000..2fb2f66
--- /dev/null
+++ b/apps/benchmark/src/implementations/emotion/index.ts
@@ -0,0 +1,11 @@
+import Box from './Box';
+import Dot from './Dot';
+import Provider from './Provider';
+import View from './View';
+
+export default {
+  Box,
+  Dot,
+  Provider,
+  View,
+};
diff --git a/apps/benchmark/src/implementations/griffel/Box.tsx b/apps/benchmark/src/implementations/griffel/Box.tsx
new file mode 100644
index 0000000..37d1fdf
--- /dev/null
+++ b/apps/benchmark/src/implementations/griffel/Box.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { BoxProps } from '../types';
+import View from './View';
+import { makeStyles, shorthands, mergeClasses } from '@griffel/react';
+
+const useStyles = makeStyles({
+  outer: {
+    alignSelf: 'flex-start',
+    ...shorthands.padding('4px'),
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  color0: {
+    backgroundColor: '#14171A',
+  },
+  color1: {
+    backgroundColor: '#AAB8C2',
+  },
+  color2: {
+    backgroundColor: '#E6ECF0',
+  },
+  color3: {
+    backgroundColor: '#FFAD1F',
+  },
+  color4: {
+    backgroundColor: '#F45D22',
+  },
+  color5: {
+    backgroundColor: '#E0245E',
+  },
+  fixed: {
+    width: '6px',
+    height: '6px',
+  },
+});
+
+const Box: React.FC<BoxProps> = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => {
+  const styles = useStyles();
+
+  return (
+    <View
+      {...other}
+      className={mergeClasses(
+        styles[`color${color}`],
+        fixed && styles.fixed,
+        layout === 'row' && styles.row,
+        outer && styles.outer,
+      )}
+    />
+  );
+};
+
+export default Box;
diff --git a/apps/benchmark/src/implementations/griffel/Dot.tsx b/apps/benchmark/src/implementations/griffel/Dot.tsx
new file mode 100644
index 0000000..69d9e1c
--- /dev/null
+++ b/apps/benchmark/src/implementations/griffel/Dot.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { DotProps } from '../types';
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useStyles = makeStyles({
+  root: {
+    position: 'absolute',
+    cursor: 'pointer',
+    width: 0,
+    height: 0,
+    ...shorthands.borderColor('transparent', 'transparent', 'var(--border-color)'),
+    ...shorthands.borderStyle('solid'),
+    ...shorthands.borderWidth('0px', 'var(--border-width)', 'var(--border-width)', 'var(--border-width)'),
+    marginLeft: 'var(--margin-left)',
+    marginTop: 'var(--margin-top)',
+    transform: 'translate(50%, 50%)',
+  },
+});
+
+const Dot: React.FC<DotProps> = ({ size, x, y, children, color }) => {
+  const styles = useStyles();
+  return (
+    <div
+      className={styles.root}
+      style={
+        {
+          '--border-width': `${size / 2}px`,
+          '--border-color': color,
+          '--margin-left': `${x}px`,
+          '--margin-top': `${y}px`,
+        } as any
+      }
+    >
+      {children}
+    </div>
+  );
+};
+
+const styles = {
+  root: {
+    position: 'absolute',
+    cursor: 'pointer',
+    width: 0,
+    height: 0,
+    borderColor: 'transparent',
+    borderStyle: 'solid',
+    borderTopWidth: 0,
+    transform: 'translate(50%, 50%)',
+  },
+} as const;
+
+export default Dot;
diff --git a/apps/benchmark/src/implementations/griffel/Provider.tsx b/apps/benchmark/src/implementations/griffel/Provider.tsx
new file mode 100644
index 0000000..bb3db34
--- /dev/null
+++ b/apps/benchmark/src/implementations/griffel/Provider.tsx
@@ -0,0 +1,14 @@
+import { createDOMRenderer } from '@griffel/core';
+import { RendererProvider } from '@griffel/react';
+import React from 'react';
+
+const renderer = createDOMRenderer(document);
+
+/**
+ * The benchmarking app already uses Griffel, no need for an extra provider here.
+ */
+const GriffelProvider: React.FC = ({ children }) => {
+  return <RendererProvider renderer={renderer}>{children}</RendererProvider>;
+};
+
+export default GriffelProvider;
diff --git a/apps/benchmark/src/implementations/griffel/View.tsx b/apps/benchmark/src/implementations/griffel/View.tsx
new file mode 100644
index 0000000..cc8c4e3
--- /dev/null
+++ b/apps/benchmark/src/implementations/griffel/View.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { makeStyles, shorthands, mergeClasses } from '@griffel/react';
+
+const useStyles = makeStyles({
+  root: {
+    alignItems: 'stretch',
+    ...shorthands.borderWidth('0px'),
+    ...shorthands.borderStyle('solid'),
+    boxSizing: 'border-box',
+    display: 'flex',
+    flexBasis: 'auto',
+    flexDirection: 'column',
+    flexShrink: 0,
+    ...shorthands.margin('0px'),
+    ...shorthands.padding('0px'),
+    position: 'relative',
+    // fix flexbox bugs
+    minHeight: 0,
+    minWidth: 0,
+  },
+});
+
+const View: React.FC<{ className: string }> = ({ className, ...other }) => {
+  const styles = useStyles();
+  return <div {...other} className={mergeClasses(styles.root, className)} />;
+};
+
+export default View;
diff --git a/apps/benchmark/src/implementations/griffel/index.ts b/apps/benchmark/src/implementations/griffel/index.ts
new file mode 100644
index 0000000..c9a332f
--- /dev/null
+++ b/apps/benchmark/src/implementations/griffel/index.ts
@@ -0,0 +1,11 @@
+import Box from './Box';
+import View from './View';
+import Dot from './Dot';
+import Provider from './Provider';
+
+export default {
+  Box,
+  View,
+  Dot,
+  Provider,
+};
diff --git a/apps/benchmark/src/implementations/index.ts b/apps/benchmark/src/implementations/index.ts
new file mode 100644
index 0000000..1281f76
--- /dev/null
+++ b/apps/benchmark/src/implementations/index.ts
@@ -0,0 +1,33 @@
+// @ts-expect-error
+import packageJson from '../../package.json';
+// Glob imports handled by esbuild plugin
+// @ts-expect-error
+import * as implementations from '../implementations/**/index.ts?(x)';
+import type { ComponentsType } from '../types';
+
+interface ImplementationsImport {
+  default: { default: any }[];
+  filenames: string[];
+}
+
+interface ImplementationType {
+  components: ComponentsType;
+  name: string;
+  version: string;
+}
+
+const implementationsObject = (implementations as ImplementationsImport).filenames.reduce((obj, filename, i) => {
+  // skip this file
+  if (filename.includes('implementations/index.ts')) {
+    return obj;
+  }
+
+  const components = implementations.default[i].default;
+  // paths are normalized '../implementations/<implem>/index.ts'
+  const name = filename.split('/')[2];
+  const version = packageJson.dependencies[name] ?? '';
+  obj[name] = { components, name, version };
+  return obj;
+}, {} as Record<string, ImplementationType>);
+
+export default implementationsObject;
diff --git a/apps/benchmark/src/implementations/inline-styles/Box.tsx b/apps/benchmark/src/implementations/inline-styles/Box.tsx
new file mode 100644
index 0000000..ea7f56c
--- /dev/null
+++ b/apps/benchmark/src/implementations/inline-styles/Box.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { BoxProps } from '../types';
+import View from './View';
+
+const Box: React.FC<BoxProps> = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
+  <View
+    {...other}
+    style={{
+      ...styles[`color${color}`],
+      ...(fixed && styles.fixed),
+      ...(layout === 'row' && styles.row),
+      ...(outer && styles.outer),
+    }}
+  />
+);
+
+const styles = {
+  outer: {
+    alignSelf: 'flex-start',
+    padding: 4,
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  color0: {
+    backgroundColor: '#14171A',
+  },
+  color1: {
+    backgroundColor: '#AAB8C2',
+  },
+  color2: {
+    backgroundColor: '#E6ECF0',
+  },
+  color3: {
+    backgroundColor: '#FFAD1F',
+  },
+  color4: {
+    backgroundColor: '#F45D22',
+  },
+  color5: {
+    backgroundColor: '#E0245E',
+  },
+  fixed: {
+    width: 6,
+    height: 6,
+  },
+} as const;
+
+export default Box;
diff --git a/apps/benchmark/src/implementations/inline-styles/Dot.tsx b/apps/benchmark/src/implementations/inline-styles/Dot.tsx
new file mode 100644
index 0000000..008b55a
--- /dev/null
+++ b/apps/benchmark/src/implementations/inline-styles/Dot.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { DotProps } from '../types';
+
+const Dot: React.FC<DotProps> = ({ size, x, y, children, color }) => (
+  <div
+    style={{
+      ...styles.root,
+      borderBottomColor: color,
+      borderRightWidth: `${size / 2}px`,
+      borderBottomWidth: `${size / 2}px`,
+      borderLeftWidth: `${size / 2}px`,
+      marginLeft: `${x}px`,
+      marginTop: `${y}px`,
+    }}
+  >
+    {children}
+  </div>
+);
+
+const styles = {
+  root: {
+    position: 'absolute',
+    cursor: 'pointer',
+    width: 0,
+    height: 0,
+    borderColor: 'transparent',
+    borderStyle: 'solid',
+    borderTopWidth: 0,
+    transform: 'translate(50%, 50%)',
+  },
+} as const;
+
+export default Dot;
diff --git a/apps/benchmark/src/implementations/inline-styles/Provider.tsx b/apps/benchmark/src/implementations/inline-styles/Provider.tsx
new file mode 100644
index 0000000..67a2a4a
--- /dev/null
+++ b/apps/benchmark/src/implementations/inline-styles/Provider.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const InlineStylesProvider: React.FC = ({ children }) => {
+  return <>{children}</>;
+};
+
+export default InlineStylesProvider;
diff --git a/apps/benchmark/src/implementations/inline-styles/View.tsx b/apps/benchmark/src/implementations/inline-styles/View.tsx
new file mode 100644
index 0000000..9d37cae
--- /dev/null
+++ b/apps/benchmark/src/implementations/inline-styles/View.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+const compose = (s1: React.CSSProperties, s2: React.CSSProperties) => {
+  if (s1 && s2) {
+    return { ...s1, ...s2 };
+  } else {
+    return s1 || s2;
+  }
+};
+
+const View: React.FC<{ style: React.CSSProperties }> = ({ style, ...other }) => {
+  return <div {...other} style={compose(viewStyle, style)} />;
+};
+
+const viewStyle = {
+  alignItems: 'stretch',
+  borderWidth: 0,
+  borderStyle: 'solid',
+  boxSizing: 'border-box',
+  display: 'flex',
+  flexBasis: 'auto',
+  flexDirection: 'column',
+  flexShrink: 0,
+  margin: 0,
+  padding: 0,
+  position: 'relative',
+  // fix flexbox bugs
+  minHeight: 0,
+  minWidth: 0,
+} as React.CSSProperties;
+
+export default View;
diff --git a/apps/benchmark/src/implementations/inline-styles/index.ts b/apps/benchmark/src/implementations/inline-styles/index.ts
new file mode 100644
index 0000000..c9a332f
--- /dev/null
+++ b/apps/benchmark/src/implementations/inline-styles/index.ts
@@ -0,0 +1,11 @@
+import Box from './Box';
+import View from './View';
+import Dot from './Dot';
+import Provider from './Provider';
+
+export default {
+  Box,
+  View,
+  Dot,
+  Provider,
+};
diff --git a/apps/benchmark/src/implementations/types.ts b/apps/benchmark/src/implementations/types.ts
new file mode 100644
index 0000000..5733752
--- /dev/null
+++ b/apps/benchmark/src/implementations/types.ts
@@ -0,0 +1,13 @@
+export interface BoxProps {
+  color: 0 | 1 | 2 | 3 | 4 | 5;
+  fixed: boolean;
+  layout: 'column' | 'row';
+  outer: boolean;
+}
+
+export interface DotProps {
+  size: number;
+  x: number;
+  y: number;
+  color: string;
+}
diff --git a/apps/benchmark/src/index.tsx b/apps/benchmark/src/index.tsx
new file mode 100644
index 0000000..1891626
--- /dev/null
+++ b/apps/benchmark/src/index.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { App } from './App';
+import './App.css';
+
+ReactDOM.render(<App />, document.getElementById('root'));
diff --git a/apps/benchmark/src/scenarios/SierpinskiTriangle/index.tsx b/apps/benchmark/src/scenarios/SierpinskiTriangle/index.tsx
new file mode 100644
index 0000000..8c85836
--- /dev/null
+++ b/apps/benchmark/src/scenarios/SierpinskiTriangle/index.tsx
@@ -0,0 +1,82 @@
+import { BenchmarkType } from '../../types';
+import React from 'react';
+import { interpolatePurples, interpolateBuPu, interpolateRdPu } from 'd3-scale-chromatic';
+
+const targetSize = 10;
+
+interface SierpinskiTriangleProps {
+  x: number;
+  y: number;
+  depth: number;
+  renderCount: number;
+  components: {
+    Dot: React.ElementType;
+  };
+  s: number;
+}
+
+class SierpinskiTriangle extends React.Component<SierpinskiTriangleProps> {
+  static displayName = 'SierpinskiTriangle';
+
+  static benchmarkType: BenchmarkType = 'update';
+
+  static defaultProps = {
+    depth: 0,
+    renderCount: 0,
+  };
+
+  override render() {
+    const { components, x, y, depth, renderCount } = this.props;
+    let { s } = this.props;
+    const { Dot } = components;
+
+    if (Dot) {
+      if (s <= targetSize) {
+        let fn;
+        switch (depth) {
+          case 1:
+            fn = interpolatePurples;
+            break;
+          case 2:
+            fn = interpolateBuPu;
+            break;
+          case 3:
+          default:
+            fn = interpolateRdPu;
+        }
+
+        // introduce randomness to ensure that repeated runs don't produce the same colors
+        const color = fn((renderCount * Math.random()) / 20);
+        return <Dot color={color} size={targetSize} x={x - targetSize / 2} y={y - targetSize / 2} />;
+      }
+
+      s /= 2;
+
+      return (
+        <React.Fragment>
+          <SierpinskiTriangle components={components} depth={1} renderCount={renderCount} s={s} x={x} y={y - s / 2} />
+          <SierpinskiTriangle
+            components={components}
+            depth={2}
+            renderCount={renderCount}
+            s={s}
+            x={x - s}
+            y={y + s / 2}
+          />
+          <SierpinskiTriangle
+            components={components}
+            depth={3}
+            renderCount={renderCount}
+            s={s}
+            x={x + s}
+            y={y + s / 2}
+          />
+        </React.Fragment>
+      );
+    } else {
+      return <span style={{ color: 'white' }}>No implementation available</span>;
+    }
+  }
+}
+
+export default SierpinskiTriangle;
diff --git a/apps/benchmark/src/scenarios/Tree/index.tsx b/apps/benchmark/src/scenarios/Tree/index.tsx
new file mode 100644
index 0000000..6fbcc16
--- /dev/null
+++ b/apps/benchmark/src/scenarios/Tree/index.tsx
@@ -0,0 +1,38 @@
+import type { BenchmarkType } from '../../types';
+import React, { Component, ElementType } from 'react';
+
+interface TreeProps {
+  breadth: number;
+  depth: number;
+  id: number;
+  wrap: number;
+  components: {
+    Box: ElementType;
+  };
+}
+
+class Tree extends Component<TreeProps> {
+  static displayName = 'Tree';
+  static benchmarkType: BenchmarkType = 'mount';
+
+  override render() {
+    const { breadth, components, depth, id, wrap } = this.props;
+    const { Box } = components;
+
+    let result = (
+      <Box color={id % 3} layout={depth % 2 === 0 ? 'column' : 'row'} outer>
+        {depth === 0 && <Box color={(id % 3) + 3} fixed />}
+        {depth !== 0 &&
+          Array.from({ length: breadth }).map((el, i) => (
+            <Tree breadth={breadth} components={components} depth={depth - 1} id={i} key={i} wrap={wrap} />
+          ))}
+      </Box>
+    );
+    for (let i = 0; i < wrap; i++) {
+      result = <Box>{result}</Box>;
+    }
+    return result;
+  }
+}
+
+export default Tree;
diff --git a/apps/benchmark/src/types.ts b/apps/benchmark/src/types.ts
new file mode 100644
index 0000000..bc68131
--- /dev/null
+++ b/apps/benchmark/src/types.ts
@@ -0,0 +1,61 @@
+import React from 'react';
+import type implementations from './implementations';
+import type tests from './tests';
+
+export type BenchResultsType = {
+  startTime: number;
+  endTime: number;
+  runTime: number;
+  sampleCount: number;
+  samples: Array<FullSampleTimingType>;
+  max: number;
+  min: number;
+  median: number;
+  mean: number;
+  meanLayout: number;
+  meanScripting: number;
+  stdDev: number;
+};
+
+export type BenchmarkName = keyof typeof tests;
+export type LibraryName = keyof typeof implementations;
+
+export interface BenchDisplayResults {
+  version?: string;
+  benchmark: BenchmarkName;
+  library: LibraryName;
+  analysis?: BenchResultsType;
+}
+
+export type SampleTimingType = {
+  scriptingStart: number;
+  scriptingEnd?: number;
+  layoutStart?: number;
+  layoutEnd?: number;
+};
+
+export type FullSampleTimingType = {
+  start: number;
+  end: number;
+  scriptingStart: number;
+  scriptingEnd: number;
+  layoutStart?: number;
+  layoutEnd?: number;
+};
+
+export interface ComponentsType {
+  Box: React.ElementType;
+  Dot: React.ElementType;
+  Provider: React.ElementType;
+  View: React.ElementType;
+}
+
+export type BenchmarkType = 'mount' | 'update' | 'unmount';
+
+export interface BenchmarkParameters {
+  Component: React.ElementType;
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  getComponentProps: Function;
+  sampleCount: number;
+  type: BenchmarkType;
+}
diff --git a/apps/benchmark/tsconfig.json b/apps/benchmark/tsconfig.json
new file mode 100644
index 0000000..b0c2a4c
--- /dev/null
+++ b/apps/benchmark/tsconfig.json
@@ -0,0 +1,18 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "allowSyntheticDefaultImports": true,
+    "forceConsistentCasingInFileNames": true,
+    "jsx": "react",
+    "outDir": "../../dist/out-tsc",
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "types": ["environment", "static-assets"],
+  "include": ["./src/**/*.ts", "./src/**/*.tsx", "./*.js"]
+}
diff --git a/apps/website/docs/react/api/shorthands.md b/apps/website/docs/react/api/shorthands.md
index 3cc8627..397f16e 100644
--- a/apps/website/docs/react/api/shorthands.md
+++ b/apps/website/docs/react/api/shorthands.md
@@ -152,6 +152,36 @@ const useClasses = makeStyles({
 });
 ```
 
+### `shorthands.gridArea`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.gridArea('auto'),
+    ...shorthands.gridArea('first', 'second'),
+    ...shorthands.gridArea(2, 4, 'span footer'),
+    ...shorthands.gridArea(2, 4, 1, 3),
+  },
+});
+```
+
+### `shorthands.inset`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.inset('10px'),
+    ...shorthands.inset('10px', '5px'),
+    ...shorthands.inset('2px', '4px', '8px'),
+    ...shorthands.inset('1px', 0, '3px', '4px'),
+  },
+});
+```
+
 ### `shorthands.margin`
 
 ```js
diff --git a/apps/website/src/components/Playground/code/app.js b/apps/website/src/components/Playground/code/app.js
index fcd523d..5342f21 100644
--- a/apps/website/src/components/Playground/code/app.js
+++ b/apps/website/src/components/Playground/code/app.js
@@ -23,7 +23,8 @@ export default function App() {
         setRules(prettified);
       },
       insertionCache: {},
-      styleElements: {},
+      stylesheets: {},
+      compareMediaQueries: () => 1,
     };
 
     styles({
diff --git a/change/@griffel-babel-preset-a2850f62-b865-421a-ae26-62e7fa6b48bd.json b/change/@griffel-babel-preset-a2850f62-b865-421a-ae26-62e7fa6b48bd.json
new file mode 100644
index 0000000..5e8aa07
--- /dev/null
+++ b/change/@griffel-babel-preset-a2850f62-b865-421a-ae26-62e7fa6b48bd.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "chore: update linaria dependencies to latest",
+  "packageName": "@griffel/babel-preset",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-37d7fdb9-d5ea-4494-87aa-2b6165c44057.json b/change/@griffel-core-37d7fdb9-d5ea-4494-87aa-2b6165c44057.json
new file mode 100644
index 0000000..7c49d95
--- /dev/null
+++ b/change/@griffel-core-37d7fdb9-d5ea-4494-87aa-2b6165c44057.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: add shorthand function for `grid-area`",
+  "packageName": "@griffel/core",
+  "email": "geoff.cox@live.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-d55dcfa4-30d9-446f-9472-ae032925fbe4.json b/change/@griffel-react-d55dcfa4-30d9-446f-9472-ae032925fbe4.json
new file mode 100644
index 0000000..f1f4599
--- /dev/null
+++ b/change/@griffel-react-d55dcfa4-30d9-446f-9472-ae032925fbe4.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "feat: export `CreateDOMRendererOptions` from `@griffel/react`",
+  "packageName": "@griffel/react",
+  "email": "lingfangao@hotmail.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-webpack-extraction-plugin-9caf7232-4d9e-47bf-8052-2361d62a3790.json b/change/@griffel-webpack-extraction-plugin-9caf7232-4d9e-47bf-8052-2361d62a3790.json
new file mode 100644
index 0000000..e9dfbf9
--- /dev/null
+++ b/change/@griffel-webpack-extraction-plugin-9caf7232-4d9e-47bf-8052-2361d62a3790.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: handle comments in output",
+  "packageName": "@griffel/webpack-extraction-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/package.json b/package.json
index 16dc2f5..675494d 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "@docusaurus/core": "2.0.0-beta.15",
     "@docusaurus/module-type-aliases": "^2.0.0-beta.15",
     "@docusaurus/preset-classic": "2.0.0-beta.15",
+    "@emotion/css": "^11.9.0",
     "@nrwl/cli": "13.4.5",
     "@nrwl/eslint-plugin-nx": "13.4.5",
     "@nrwl/jest": "13.4.5",
@@ -52,15 +53,18 @@
     "@tsconfig/docusaurus": "^1.0.4",
     "@types/babel__helper-plugin-utils": "7.10.0",
     "@types/chrome": "0.0.180",
+    "@types/d3-scale-chromatic": "^3.0.0",
     "@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",
     "@types/stylis": "4.0.2",
     "@types/tmp": "0.2.3",
     "@types/webpack-env": "^1.17.0",
+    "@types/yargs": "^17.0.10",
     "@typescript-eslint/eslint-plugin": "~5.3.0",
     "@typescript-eslint/parser": "~5.3.0",
     "babel-jest": "27.2.3",
@@ -69,6 +73,10 @@
     "babel-plugin-tester": "10.0.0",
     "beachball": "^2.20.0",
     "bestzip": "2.2.0",
+    "d3-scale-chromatic": "^3.0.0",
+    "esbuild": "^0.14.43",
+    "esbuild-plugin-alias": "^0.2.1",
+    "esbuild-plugin-import-glob": "^0.1.1",
     "eslint": "8.2.0",
     "eslint-config-prettier": "8.1.0",
     "eslint-import-resolver-typescript": "2.5.0",
@@ -80,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",
@@ -93,7 +102,8 @@
     "tmp": "0.2.1",
     "ts-jest": "27.0.5",
     "typescript": "~4.5.2",
-    "url-loader": "^3.0.0"
+    "url-loader": "^3.0.0",
+    "yargs": "^17.5.1"
   },
   "dependencies": {
     "@babel/core": "^7.12.13",
@@ -102,8 +112,8 @@
     "@babel/template": "^7.12.13",
     "@babel/traverse": "^7.12.13",
     "@emotion/hash": "^0.8.0",
-    "@linaria/babel-preset": "^3.0.0-beta.14",
-    "@linaria/shaker": "^3.0.0-beta.14",
+    "@linaria/babel-preset": "^3.0.0-beta.22",
+    "@linaria/shaker": "^3.0.0-beta.22",
     "@typescript-eslint/utils": "^5.20.0",
     "ajv": "^8.4.0",
     "csstype": "^3.0.10",
diff --git a/packages/babel-preset/CHANGELOG.json b/packages/babel-preset/CHANGELOG.json
index 5665d6f..3fff441 100644
--- a/packages/babel-preset/CHANGELOG.json
+++ b/packages/babel-preset/CHANGELOG.json
@@ -1,6 +1,36 @@
 {
   "name": "@griffel/babel-preset",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/babel-preset_v1.3.3",
+      "version": "1.3.3",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/babel-preset",
+            "comment": "Bump @griffel/core to v1.4.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..9bd07b3 100644
--- a/packages/babel-preset/CHANGELOG.md
+++ b/packages/babel-preset/CHANGELOG.md
@@ -1,9 +1,25 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.3.3
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.1
+
+## 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/README.md b/packages/babel-preset/README.md
index 88652bb..586825e 100644
--- a/packages/babel-preset/README.md
+++ b/packages/babel-preset/README.md
@@ -7,7 +7,7 @@ A Babel preset that performs build time transforms for [`@griffel/react`](../rea
 ```bash
 yarn add --dev @griffel/babel-preset
 # or
-npm install --dev @griffel/babel-preset
+npm install --save-dev @griffel/babel-preset
 ```
 
 ## When to use it?
diff --git a/packages/babel-preset/__fixtures__/rules-with-metadata/code.ts b/packages/babel-preset/__fixtures__/rules-with-metadata/code.ts
new file mode 100644
index 0000000..0e4b2e6
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/rules-with-metadata/code.ts
@@ -0,0 +1,9 @@
+import { makeStyles } from '@griffel/react';
+
+export const useStyles = makeStyles({
+  media: {
+    '@media screen and (max-width: 100px)': {
+      color: 'red',
+    },
+  },
+});
diff --git a/packages/babel-preset/__fixtures__/rules-with-metadata/output.ts b/packages/babel-preset/__fixtures__/rules-with-metadata/output.ts
new file mode 100644
index 0000000..f4541c5
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/rules-with-metadata/output.ts
@@ -0,0 +1,18 @@
+import { __styles } from '@griffel/react';
+export const useStyles = __styles(
+  {
+    media: {
+      Bulngiv: 'fr5o61b',
+    },
+  },
+  {
+    m: [
+      [
+        '@media screen and (max-width: 100px){.fr5o61b{color:red;}}',
+        {
+          m: 'screen and (max-width: 100px)',
+        },
+      ],
+    ],
+  },
+);
diff --git a/packages/babel-preset/package.json b/packages/babel-preset/package.json
index 41f7fcc..dd248c1 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.3",
   "description": "Babel preset with build time transforms for Griffel",
   "license": "MIT",
   "repository": {
@@ -13,12 +13,12 @@
     "@babel/helper-plugin-utils": "^7.12.13",
     "@babel/template": "^7.12.13",
     "@babel/traverse": "^7.12.13",
-    "@linaria/babel-preset": "^3.0.0-beta.14",
-    "@linaria/shaker": "^3.0.0-beta.14",
+    "@linaria/babel-preset": "^3.0.0-beta.22",
+    "@linaria/shaker": "^3.0.0-beta.22",
     "ajv": "^8.4.0",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.3.1"
+    "@griffel/core": "^1.4.1"
   }
 }
diff --git a/packages/babel-preset/src/utils/evaluatePathsInVM.ts b/packages/babel-preset/src/utils/evaluatePathsInVM.ts
index aa34a6c..f92296b 100644
--- a/packages/babel-preset/src/utils/evaluatePathsInVM.ts
+++ b/packages/babel-preset/src/utils/evaluatePathsInVM.ts
@@ -27,7 +27,7 @@ function evaluate(code: string, filename: string, pluginOptions: Required<BabelP
 
   mod.evaluate(code, [EVAL_EXPORT_NAME]);
 
-  return mod.exports[EVAL_EXPORT_NAME];
+  return (mod.exports as { [EVAL_EXPORT_NAME]: unknown[] })[EVAL_EXPORT_NAME];
 }
 
 function findFreeName(scope: Scope, name: string): string {
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..799235d 100644
--- a/packages/core/CHANGELOG.json
+++ b/packages/core/CHANGELOG.json
@@ -1,6 +1,68 @@
 {
   "name": "@griffel/core",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/core_v1.4.1",
+      "version": "1.4.1",
+      "comments": {
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/core",
+            "commit": "13f45f365ef157651ef6dc11651fda94aa9f09a5",
+            "comment": "chore: export getStyleBucketName & normalizeCSSBucketEntry functions for internal usage"
+          }
+        ]
+      }
+    },
+    {
+      "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..3678786 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,9 +1,33 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.4.1
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- chore: export getStyleBucketName & normalizeCSSBucketEntry functions for internal usage (olfedias@microsoft.com)
+
+## 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..f94172f 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.1",
   "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/__styles.ts b/packages/core/src/__styles.ts
index 6ad52c7..0c3b429 100644
--- a/packages/core/src/__styles.ts
+++ b/packages/core/src/__styles.ts
@@ -1,3 +1,4 @@
+import { debugData, isDevToolsEnabled } from './devtools';
 import { reduceToClassNameForSlots } from './runtime/reduceToClassNameForSlots';
 import { MakeStylesOptions, CSSClassesMapBySlot, CSSRulesByBucket } from './types';
 
@@ -37,7 +38,15 @@ export function __styles<Slots extends string>(
       insertionCache[rendererId] = true;
     }
 
-    return isLTR ? (ltrClassNamesForSlots as Record<Slots, string>) : (rtlClassNamesForSlots as Record<Slots, string>);
+    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/common/snapshotSerializers.ts b/packages/core/src/common/snapshotSerializers.ts
index c2ec844..156e2e1 100644
--- a/packages/core/src/common/snapshotSerializers.ts
+++ b/packages/core/src/common/snapshotSerializers.ts
@@ -3,6 +3,7 @@
 import * as prettier from 'prettier';
 
 import { resolveStyleRules } from '../runtime/resolveStyleRules';
+import { normalizeCSSBucketEntry } from '../runtime/utils/normalizeCSSBucketEntry';
 import type { GriffelRenderer } from '../types';
 
 // eslint-disable-next-line eqeqeq
@@ -10,7 +11,7 @@ const isObject = (value: unknown) => value != null && !Array.isArray(value) && t
 
 export const griffelRendererSerializer: jest.SnapshotSerializerPlugin = {
   test(value) {
-    return isObject(value) && isObject(value.styleElements);
+    return isObject(value) && isObject(value.stylesheets);
   },
   print(value) {
     /**
@@ -18,15 +19,15 @@ export const griffelRendererSerializer: jest.SnapshotSerializerPlugin = {
      */
     const _value = value as GriffelRenderer;
 
-    const styleElementsKeys = Object.keys(_value.styleElements) as (keyof typeof _value['styleElements'])[];
+    const stylesheetKeys = Object.keys(_value.stylesheets) as (keyof typeof _value['stylesheets'])[];
 
-    const rules = styleElementsKeys.reduce((acc, styleEl) => {
-      const styleElement = _value.styleElements[styleEl];
+    const rules = stylesheetKeys.reduce((acc, styleEl) => {
+      const stylesheet = _value.stylesheets[styleEl];
 
-      if (styleElement) {
-        const cssRules = styleElement.sheet ? Array.from(styleElement.sheet.cssRules) : [];
+      if (stylesheet) {
+        const cssRules = stylesheet.cssRules() ?? ([] as string[]);
 
-        return [...acc, ...cssRules.map(rule => rule.cssText)];
+        return [...acc, ...cssRules];
       }
 
       return acc;
@@ -53,7 +54,7 @@ export const griffelRulesSerializer: jest.SnapshotSerializerPlugin = {
     const keys = Object.keys(cssRulesByBucket) as (keyof typeof cssRulesByBucket)[];
 
     return keys.reduce((acc, styleBucketName) => {
-      const rules = cssRulesByBucket[styleBucketName]!;
+      const rules = cssRulesByBucket[styleBucketName]!.map(entry => normalizeCSSBucketEntry(entry)[0]);
 
       return prettier.format(acc + rules.join(''), { parser: 'css' }).trim();
     }, '');
diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts
index 2bfd62a..4c49715 100644
--- a/packages/core/src/constants.ts
+++ b/packages/core/src/constants.ts
@@ -1,6 +1,9 @@
 import * as CSS from 'csstype';
 import { LookupItem, SequenceHash } from './types';
 
+/** @internal */
+export const DATA_BUCKET_ATTR = 'data-make-styles-bucket';
+
 /** @internal */
 export const HASH_PREFIX = 'f';
 
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 0de3503..de694f7 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -12,6 +12,7 @@ import {
   borderWidth,
   flex,
   gap,
+  gridArea,
   margin,
   padding,
   overflow,
@@ -31,6 +32,7 @@ export const shorthands = {
   borderWidth,
   flex,
   gap,
+  gridArea,
   margin,
   padding,
   overflow,
@@ -40,7 +42,6 @@ export const shorthands = {
 
 export { createDOMRenderer } from './renderer/createDOMRenderer';
 export type { CreateDOMRendererOptions } from './renderer/createDOMRenderer';
-export { styleBucketOrdering } from './renderer/getStyleSheetForBucket';
 export { rehydrateRendererCache } from './renderer/rehydrateRendererCache';
 
 export { mergeClasses } from './mergeClasses';
@@ -48,7 +49,13 @@ export { makeStaticStyles } from './makeStaticStyles';
 export { makeStyles } from './makeStyles';
 export { resolveStyleRulesForSlots } from './resolveStyleRulesForSlots';
 
-// Private exports, are used by build time transforms
+// Private exports, are used by build time transforms or other tools
+export { __css } from './__css';
+export { normalizeCSSBucketEntry } from './runtime/utils/normalizeCSSBucketEntry';
+export { styleBucketOrdering } from './renderer/getStyleSheetForBucket';
+export { defaultCompareMediaQueries } from './renderer/createDOMRenderer';
+export { getStyleBucketName } from './runtime/getStyleBucketName';
+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 0f36b4d..805edd1 100644
--- a/packages/core/src/renderer/createDOMRenderer.ts
+++ b/packages/core/src/renderer/createDOMRenderer.ts
@@ -1,4 +1,5 @@
 import { injectDevTools, isDevToolsEnabled, debugData } from '../devtools';
+import { normalizeCSSBucketEntry } from '../runtime/utils/normalizeCSSBucketEntry';
 import { GriffelRenderer, StyleBucketName } from '../types';
 import { getStyleSheetForBucket } from './getStyleSheetForBucket';
 
@@ -21,8 +22,18 @@ 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;
 }
 
+/** @internal */
+export const defaultCompareMediaQueries = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
+
 /**
  * Creates a new instances of a renderer.
  *
@@ -32,10 +43,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: {},
-    styleElements: {},
+    stylesheets: {},
+    compareMediaQueries,
 
     id: `d${lastIndex++}`,
 
@@ -43,13 +55,17 @@ export function createDOMRenderer(
       // eslint-disable-next-line guard-for-in
       for (const styleBucketName in cssRules) {
         const cssRulesForBucket = cssRules[styleBucketName as StyleBucketName]!;
-        const sheet =
-          target &&
-          getStyleSheetForBucket(styleBucketName as StyleBucketName, target, renderer, options.styleElementAttributes);
 
         // This is a hot path in rendering styles: ".length" is cached in "l" var to avoid accesses the property
         for (let i = 0, l = cssRulesForBucket.length; i < l; i++) {
-          const ruleCSS = cssRulesForBucket[i];
+          const [ruleCSS, metadata] = normalizeCSSBucketEntry(cssRulesForBucket[i]);
+          const sheet = getStyleSheetForBucket(
+            styleBucketName as StyleBucketName,
+            target,
+            renderer,
+            options.styleElementAttributes,
+            metadata,
+          );
 
           if (renderer.insertionCache[ruleCSS]) {
             continue;
@@ -60,21 +76,19 @@ export function createDOMRenderer(
             debugData.addCSSRule(ruleCSS);
           }
 
-          if (sheet) {
-            try {
-              if (unstable_filterCSSRule) {
-                if (unstable_filterCSSRule(ruleCSS)) {
-                  sheet.insertRule(ruleCSS, sheet.cssRules.length);
-                }
-              } else {
-                sheet.insertRule(ruleCSS, sheet.cssRules.length);
-              }
-            } catch (e) {
-              // We've disabled these warnings due to false-positive errors with browser prefixes
-              if (process.env.NODE_ENV !== 'production' && !ignoreSuffixesRegex.test(ruleCSS)) {
-                // eslint-disable-next-line no-console
-                console.error(`There was a problem inserting the following rule: "${ruleCSS}"`, e);
+          try {
+            if (unstable_filterCSSRule) {
+              if (unstable_filterCSSRule(ruleCSS)) {
+                sheet.insertRule(ruleCSS);
               }
+            } else {
+              sheet.insertRule(ruleCSS);
+            }
+          } catch (e) {
+            // We've disabled these warnings due to false-positive errors with browser prefixes
+            if (process.env.NODE_ENV !== 'production' && !ignoreSuffixesRegex.test(ruleCSS)) {
+              // eslint-disable-next-line no-console
+              console.error(`There was a problem inserting the following rule: "${ruleCSS}"`, e);
             }
           }
         }
diff --git a/packages/core/src/renderer/createIsomorphicStyleSheet.ts b/packages/core/src/renderer/createIsomorphicStyleSheet.ts
new file mode 100644
index 0000000..56860cf
--- /dev/null
+++ b/packages/core/src/renderer/createIsomorphicStyleSheet.ts
@@ -0,0 +1,53 @@
+import { DATA_BUCKET_ATTR } from '../constants';
+import { IsomorphicStyleSheet, StyleBucketName } from '../types';
+
+export function createIsomorphicStyleSheet(
+  styleElement: HTMLStyleElement | undefined,
+  bucketName: StyleBucketName,
+  elementAttributes: Record<string, string>,
+): IsomorphicStyleSheet {
+  // no CSSStyleSheet in SSR, just append rules here for server render
+  const __cssRulesForSSR: string[] = [];
+
+  elementAttributes[DATA_BUCKET_ATTR] = bucketName;
+  if (styleElement) {
+    for (const attrName in elementAttributes) {
+      styleElement.setAttribute(attrName, elementAttributes[attrName]);
+    }
+  }
+
+  function insertRule(rule: string) {
+    if (styleElement?.sheet) {
+      return styleElement.sheet.insertRule(rule, styleElement.sheet.cssRules.length);
+    }
+
+    return __cssRulesForSSR.push(rule);
+  }
+
+  return {
+    elementAttributes,
+    insertRule,
+    element: styleElement,
+    bucketName,
+    cssRules() {
+      if (styleElement?.sheet) {
+        return Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText);
+      }
+
+      return __cssRulesForSSR;
+    },
+  };
+}
+
+export function createIsomorphicStyleSheetFromElement(element: HTMLStyleElement) {
+  const elementAttributes = Array.from(element.attributes).reduce((acc, attr) => {
+    acc[attr.name] = attr.value;
+    return acc;
+  }, {} as Record<string, string>);
+  const stylesheet = createIsomorphicStyleSheet(
+    element,
+    elementAttributes[DATA_BUCKET_ATTR] as StyleBucketName,
+    elementAttributes,
+  );
+  return stylesheet;
+}
diff --git a/packages/core/src/renderer/getStyleSheetForBucket.ts b/packages/core/src/renderer/getStyleSheetForBucket.ts
index 9e59742..3abc69c 100644
--- a/packages/core/src/renderer/getStyleSheetForBucket.ts
+++ b/packages/core/src/renderer/getStyleSheetForBucket.ts
@@ -1,9 +1,11 @@
-import { GriffelRenderer, StyleBucketName } from '../types';
+import { DATA_BUCKET_ATTR } from '../constants';
+import { GriffelRenderer, IsomorphicStyleSheet, StyleBucketName } from '../types';
+import { createIsomorphicStyleSheet } from './createIsomorphicStyleSheet';
 
 /**
  * Ordered style buckets using their short pseudo name.
  *
- * @private
+ * @internal
  */
 export const styleBucketOrdering: StyleBucketName[] = [
   // catch-all
@@ -26,41 +28,92 @@ export const styleBucketOrdering: StyleBucketName[] = [
   'k',
   // at-rules
   't',
+  // @media rules
+  '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.
  */
 export function getStyleSheetForBucket(
   bucketName: StyleBucketName,
-  target: Document,
+  target: Document | undefined,
   renderer: GriffelRenderer,
   elementAttributes: Record<string, string> = {},
-): CSSStyleSheet {
-  if (!renderer.styleElements[bucketName]) {
-    let currentBucketIndex = styleBucketOrdering.indexOf(bucketName) + 1;
-    let nextBucketFromCache = null;
+  metadata?: Record<string, unknown>,
+): IsomorphicStyleSheet {
+  let stylesheetKey: StyleBucketName | string = bucketName;
+
+  if (bucketName === 'm' && metadata) {
+    stylesheetKey = (bucketName + metadata['m']) as string;
+  }
+
+  if (!renderer.stylesheets[stylesheetKey]) {
+    const tag: HTMLStyleElement | undefined = target && target.createElement('style');
 
-    // Find the next bucket which we will add our new style bucket before.
-    for (; currentBucketIndex < styleBucketOrdering.length; currentBucketIndex++) {
-      const nextBucket = renderer.styleElements[styleBucketOrdering[currentBucketIndex]];
-      if (nextBucket) {
-        nextBucketFromCache = nextBucket;
-        break;
-      }
+    if (bucketName === 'm' && metadata) {
+      elementAttributes['media'] = metadata['m'] as string;
     }
 
-    const tag = target.createElement('style');
+    const stylesheet = createIsomorphicStyleSheet(tag, bucketName, elementAttributes);
+    renderer.stylesheets[stylesheetKey] = stylesheet;
 
-    tag.dataset['makeStylesBucket'] = bucketName;
+    if (target && tag) {
+      const elementSibling = findElementSibling(target, bucketName, renderer, metadata);
+      target.head.insertBefore(tag, elementSibling);
+    }
+  }
 
-    for (const attribute in elementAttributes) {
-      tag.setAttribute(attribute, elementAttributes[attribute]);
+  return renderer.stylesheets[stylesheetKey]!;
+}
+
+/**
+ * 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);
     }
+  }
 
-    renderer.styleElements[bucketName] = tag;
-    target.head.insertBefore(tag, nextBucketFromCache);
+  for (const styleElement of styleElements) {
+    if (comparer(styleElement) < 0) {
+      return styleElement;
+    }
   }
 
-  return renderer.styleElements[bucketName]!.sheet as CSSStyleSheet;
+  return null;
 }
diff --git a/packages/core/src/renderer/rehydrateRendererCache.ts b/packages/core/src/renderer/rehydrateRendererCache.ts
index 5ba2986..1961913 100644
--- a/packages/core/src/renderer/rehydrateRendererCache.ts
+++ b/packages/core/src/renderer/rehydrateRendererCache.ts
@@ -1,4 +1,5 @@
 import { GriffelRenderer, StyleBucketName } from '../types';
+import { createIsomorphicStyleSheetFromElement } from './createIsomorphicStyleSheet';
 
 // Regexps to extract names of classes and animations
 // https://github.com/styletron/styletron/blob/e0fcae826744eb00ce679ac613a1b10d44256660/packages/styletron-engine-atomic/src/client/client.js#L8
@@ -9,6 +10,7 @@ const STYLES_HYDRATOR = /\.([^{:]+)(:[^{]+)?{(?:[^}]*;)?([^}]*?)}/g;
 const regexps: Partial<Record<StyleBucketName, RegExp>> = {
   k: KEYFRAMES_HYDRATOR,
   t: AT_RULES_HYDRATOR,
+  m: AT_RULES_HYDRATOR,
 };
 
 /**
@@ -28,9 +30,11 @@ export function rehydrateRendererCache(
       const bucketName = styleElement.dataset['makeStylesBucket'] as StyleBucketName;
       const regex = regexps[bucketName] || STYLES_HYDRATOR;
 
+      const stylesheetKey = bucketName === 'm' ? bucketName + styleElement.media : bucketName;
+
       // 👇 If some elements are not created yet, we will register them in renderer
-      if (!renderer.styleElements[bucketName]) {
-        renderer.styleElements[bucketName] = styleElement;
+      if (!renderer.stylesheets[stylesheetKey]) {
+        renderer.stylesheets[stylesheetKey] = createIsomorphicStyleSheetFromElement(styleElement);
       }
 
       let match;
diff --git a/packages/core/src/runtime/getStyleBucketName.ts b/packages/core/src/runtime/getStyleBucketName.ts
index 1bab581..108906a 100644
--- a/packages/core/src/runtime/getStyleBucketName.ts
+++ b/packages/core/src/runtime/getStyleBucketName.ts
@@ -39,10 +39,16 @@ const pseudosMap: Record<string, StyleBucketName | undefined> = {
  * "h"
  * "f"
  * ```
+ *
+ * @internal
  */
 export function getStyleBucketName(pseudo: string, layer: string, media: string, support: string): StyleBucketName {
-  // We are grouping all the at-rules like @media, @supports etc under `t` bucket.
-  if (media || layer || support) {
+  if (media) {
+    return 'm';
+  }
+
+  // We are grouping all the at-rules like @supports etc under `t` bucket.
+  if (layer || support) {
     return 't';
   }
 
diff --git a/packages/core/src/runtime/reduceToClassNameForSlots.ts b/packages/core/src/runtime/reduceToClassNameForSlots.ts
index f96997f..270e7c8 100644
--- a/packages/core/src/runtime/reduceToClassNameForSlots.ts
+++ b/packages/core/src/runtime/reduceToClassNameForSlots.ts
@@ -32,7 +32,7 @@ export function reduceToClassName(classMap: CSSClassesMap, dir: 'ltr' | 'rtl'):
  * Reduces classname maps for slots to classname strings. Registers them in a definition cache to be used by
  * `mergeClasses()`.
  *
- * @private
+ * @internal
  */
 export function reduceToClassNameForSlots<Slots extends string | number>(
   classesMapBySlot: CSSClassesMapBySlot<Slots>,
diff --git a/packages/core/src/runtime/resolveStyleRules.ts b/packages/core/src/runtime/resolveStyleRules.ts
index 6a368db..8d0e1f8 100644
--- a/packages/core/src/runtime/resolveStyleRules.ts
+++ b/packages/core/src/runtime/resolveStyleRules.ts
@@ -2,7 +2,14 @@ import hashString from '@emotion/hash';
 import { convert, convertProperty } from 'rtl-css-js/core';
 
 import { HASH_PREFIX } from '../constants';
-import { GriffelStyle, CSSClassesMap, CSSRulesByBucket, StyleBucketName, GriffelAnimation } from '../types';
+import {
+  GriffelStyle,
+  CSSClassesMap,
+  CSSRulesByBucket,
+  StyleBucketName,
+  GriffelAnimation,
+  CSSBucketEntry,
+} from '../types';
 import { compileCSS, CompileCSSOptions } from './compileCSS';
 import { compileKeyframeRule, compileKeyframesCSS } from './compileKeyframeCSS';
 import { generateCombinedQuery } from './utils/generateCombinedMediaQuery';
@@ -26,17 +33,31 @@ function pushToClassesMap(
   classesMap[propertyKey] = rtlClassname ? [ltrClassname!, rtlClassname] : ltrClassname;
 }
 
+function createBucketEntry(cssRule: string, metadata: Record<string, unknown> | undefined): CSSBucketEntry {
+  if (metadata) {
+    return [cssRule, metadata];
+  }
+
+  return cssRule;
+}
+
 function pushToCSSRules(
   cssRulesByBucket: CSSRulesByBucket,
   styleBucketName: StyleBucketName,
   ltrCSS: string,
   rtlCSS: string | undefined,
+  media: string | undefined,
 ) {
-  cssRulesByBucket[styleBucketName] = cssRulesByBucket[styleBucketName] || [];
-  cssRulesByBucket[styleBucketName]!.push(ltrCSS);
+  let metadata: Record<string, unknown> | undefined;
+  if (styleBucketName === 'm' && media) {
+    metadata = { m: media };
+  }
+
+  cssRulesByBucket[styleBucketName] ??= [];
+  cssRulesByBucket[styleBucketName]!.push(createBucketEntry(ltrCSS, metadata));
 
   if (rtlCSS) {
-    cssRulesByBucket[styleBucketName]!.push(rtlCSS);
+    cssRulesByBucket[styleBucketName]!.push(createBucketEntry(rtlCSS, metadata));
   }
 }
 
@@ -128,7 +149,7 @@ export function resolveStyleRules(
       });
 
       pushToClassesMap(cssClassesMap, key, className, rtlClassName);
-      pushToCSSRules(cssRulesByBucket, styleBucketName, ltrCSS, rtlCSS);
+      pushToCSSRules(cssRulesByBucket, styleBucketName, ltrCSS, rtlCSS, media);
     } else if (property === 'animationName') {
       const animationNameValue = Array.isArray(value) ? (value as GriffelAnimation[]) : [value as GriffelAnimation];
 
@@ -160,6 +181,7 @@ export function resolveStyleRules(
             'k',
             keyframeRules[i],
             rtlKeyframeRules[i],
+            media,
           );
         }
 
@@ -245,7 +267,7 @@ export function resolveStyleRules(
       });
 
       pushToClassesMap(cssClassesMap, key, className, rtlClassName);
-      pushToCSSRules(cssRulesByBucket, styleBucketName, ltrCSS, rtlCSS);
+      pushToCSSRules(cssRulesByBucket, styleBucketName, ltrCSS, rtlCSS, media);
     } else if (isObject(value)) {
       if (isNestedSelector(property)) {
         resolveStyleRules(
diff --git a/packages/core/src/runtime/utils/normalizeCSSBucketEntry.ts b/packages/core/src/runtime/utils/normalizeCSSBucketEntry.ts
new file mode 100644
index 0000000..91958b4
--- /dev/null
+++ b/packages/core/src/runtime/utils/normalizeCSSBucketEntry.ts
@@ -0,0 +1,21 @@
+import { CSSBucketEntry } from '../../types';
+
+/**
+ * @internal
+ *
+ * @param entry - CSS bucket entry that can be either a string or an array
+ * @returns An array where the first element is the CSS rule
+ */
+export function normalizeCSSBucketEntry(entry: CSSBucketEntry): [string] | [string, Record<string, unknown>] {
+  if (!Array.isArray(entry)) {
+    return [entry];
+  }
+
+  if (process.env.NODE_ENV !== 'production' && entry.length > 2) {
+    throw new Error(
+      'CSS Bucket contains an entry with greater than 2 items, please report this to https://github.com/microsoft/griffel/issues',
+    );
+  }
+
+  return entry;
+}
diff --git a/packages/core/src/shorthands/gridArea.ts b/packages/core/src/shorthands/gridArea.ts
new file mode 100644
index 0000000..cef97dc
--- /dev/null
+++ b/packages/core/src/shorthands/gridArea.ts
@@ -0,0 +1,89 @@
+import type { GriffelStylesStrictCSSObject } from '../types';
+import { GridAreaInput } from './types';
+
+type GridAreaStyle = Pick<
+  GriffelStylesStrictCSSObject,
+  'gridRowStart' | 'gridRowEnd' | 'gridColumnStart' | 'gridColumnEnd'
+>;
+
+const cssVarRegEx = /var\(.*\)/gi;
+
+function isValidGridAreaInput(value: GridAreaInput) {
+  return value === undefined || typeof value === 'number' || (typeof value === 'string' && !cssVarRegEx.test(value));
+}
+
+// A custom-ident can be an alpha-numeric string including dash (-), underscore, escaped (\) characters, and escaped unicode
+const customIdentRegEx = /^[a-zA-Z0-9\-_\\#;]+$/;
+const nonCustomIdentRegEx = /^-moz-initial$|^auto$|^initial$|^inherit$|^revert$|^unset$|^span \d+$|\d.*/;
+
+// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
+function isCustomIdent(value: GridAreaInput | undefined): boolean {
+  return (
+    value !== undefined && typeof value === 'string' && customIdentRegEx.test(value) && !nonCustomIdentRegEx.test(value)
+  );
+}
+
+export function gridArea(all: GridAreaInput): GridAreaStyle;
+export function gridArea(rowStart: GridAreaInput, columnStart: GridAreaInput): GridAreaStyle;
+export function gridArea(rowStart: GridAreaInput, columnStart: GridAreaInput, rowEnd: GridAreaInput): GridAreaStyle;
+export function gridArea(
+  rowStart: GridAreaInput,
+  columnStart: GridAreaInput,
+  rowEnd: GridAreaInput,
+  columnEnd: GridAreaInput,
+): GridAreaStyle;
+
+/**
+ * A function that implements CSS spec conformant expansion for "grid-area"
+ *
+ * @example
+ *   gridArea('auto')
+ *   gridArea('first', 'second')
+ *   gridArea(2, 4, 4)
+ *   gridArea(2, 4, 1, 3)
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/grid-area
+ */
+export function gridArea(...values: GridAreaInput[]): GridAreaStyle {
+  // if any value is not valid, then do not apply the CSS.
+  if (values.some(value => !isValidGridAreaInput(value))) {
+    if (process.env.NODE_ENV !== 'production') {
+      // eslint-disable-next-line no-console
+      console.error(
+        `The value passed to shorthands.gridArea() did not match any gridArea property specs. The CSS styles were not generated. Please, check the gridArea documentation.`,
+        [
+          'The value passed to shorthands.gridArea() did not match any gridArea property specs. ',
+          'The CSS styles were not generated.\n',
+          'Please, check the `grid-area` documentation:\n',
+          '- https://developer.mozilla.org/docs/Web/CSS/grid-area',
+          '- https://griffel.js.org/react/api/shorthands#shorthandsgridarea',
+        ].join(''),
+      );
+    }
+
+    return {};
+  }
+  const gridRowStart = values[0] !== undefined ? values[0] : 'auto';
+
+  // When grid-column-start is omitted, if grid-row-start is a <custom-ident>,
+  // all four longhands are set to that value.
+  // Otherwise, it is set to auto.
+  const gridColumnStart = values[1] !== undefined ? values[1] : isCustomIdent(gridRowStart) ? gridRowStart : 'auto';
+
+  // When grid-row-end is omitted,
+  // if grid-row-start is a <custom-ident>, grid-row-end is set to that <custom-ident>;
+  // otherwise, it is set to auto.
+  const gridRowEnd = values[2] !== undefined ? values[2] : isCustomIdent(gridRowStart) ? gridRowStart : 'auto';
+
+  // When grid-column-end is omitted,
+  // if grid-column-start is a <custom-ident>, grid-column-end is set to that <custom-ident>;
+  // otherwise, it is set to auto.
+  const gridColumnEnd = values[3] !== undefined ? values[3] : isCustomIdent(gridColumnStart) ? gridColumnStart : 'auto';
+
+  return {
+    gridRowStart,
+    gridColumnStart,
+    gridRowEnd,
+    gridColumnEnd,
+  };
+}
diff --git a/packages/core/src/shorthands/index.ts b/packages/core/src/shorthands/index.ts
index c710e9a..aec15e7 100644
--- a/packages/core/src/shorthands/index.ts
+++ b/packages/core/src/shorthands/index.ts
@@ -9,6 +9,7 @@ export { borderRadius } from './borderRadius';
 export { borderWidth } from './borderWidth';
 export { flex } from './flex';
 export { gap } from './gap';
+export { gridArea } from './gridArea';
 export { margin } from './margin';
 export { padding } from './padding';
 export { overflow } from './overflow';
diff --git a/packages/core/src/shorthands/types.ts b/packages/core/src/shorthands/types.ts
index 2829c58..22e37c1 100644
--- a/packages/core/src/shorthands/types.ts
+++ b/packages/core/src/shorthands/types.ts
@@ -13,6 +13,8 @@ export type BorderRadiusInput = ValueOrArray<CSS.Property.BorderRadius<GriffelSt
 
 export type GapInput = ValueOrArray<CSS.Property.Gap<GriffelStylesCSSValue>>;
 
+export type GridAreaInput = CSS.Property.GridArea;
+
 export type MarginInput = ValueOrArray<CSS.Property.Margin<GriffelStylesCSSValue>>;
 
 export type OverflowInput = ValueOrArray<CSS.Property.Overflow>;
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index ec71957..fd3c8ec 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -85,6 +85,23 @@ export interface MakeStaticStylesOptions {
   renderer: GriffelRenderer;
 }
 
+export interface IsomorphicStyleSheet {
+  /**
+   * Attributes applied to the underlying HTMLStyleElement
+   */
+  elementAttributes: Record<string, string>;
+  /**
+   * Underlying HTMLStyleElement
+   */
+  element: HTMLStyleElement | undefined;
+  bucketName: StyleBucketName;
+  /**
+   * Returns all CSS rules on the stylesheet
+   */
+  cssRules(): string[];
+  insertRule(rule: string): number | undefined;
+}
+
 export interface GriffelRenderer {
   id: string;
 
@@ -96,48 +113,57 @@ export interface GriffelRenderer {
   /**
    * @private
    */
-  styleElements: Partial<Record<StyleBucketName, HTMLStyleElement>>;
+  stylesheets: { [key in StyleBucketName]?: IsomorphicStyleSheet } & Record<string, IsomorphicStyleSheet>;
 
   /**
    * @private
    */
   insertCSSRules(cssRules: CSSRulesByBucket): void;
+
+  /**
+   * @private
+   */
+  compareMediaQueries(a: string, b: string): number;
 }
 
 /**
  * Buckets under which we will group our stylesheets.
  */
-export type StyleBucketName =
+export type StyleBucketName = keyof CSSRulesByBucket;
+export type SequenceHash = string;
+export type PropertyHash = string;
+
+export type CSSClasses = /* ltrClassName */ string | [/* ltrClassName */ string, /* rtlClassName */ string];
+
+export type CSSClassesMap = Record<PropertyHash, CSSClasses>;
+export type CSSClassesMapBySlot<Slots extends string | number> = Record<Slots, CSSClassesMap>;
+
+export type CSSRulesByBucket = {
   // default
-  | 'd'
+  d?: CSSBucketEntry[];
   // link
-  | 'l'
+  l?: CSSBucketEntry[];
   // visited
-  | 'v'
+  v?: CSSBucketEntry[];
   // focus-within
-  | 'w'
+  w?: CSSBucketEntry[];
   // focus
-  | 'f'
+  f?: CSSBucketEntry[];
   // focus-visible
-  | 'i'
+  i?: CSSBucketEntry[];
   // hover
-  | 'h'
+  h?: CSSBucketEntry[];
   // active
-  | 'a'
+  a?: CSSBucketEntry[];
   // @keyframes definitions
-  | 'k'
-  // at-rules (@media, @support, @layer)
-  | 't';
-
-export type SequenceHash = string;
-export type PropertyHash = string;
-
-export type CSSClasses = /* ltrClassName */ string | [/* ltrClassName */ string, /* rtlClassName */ string];
-
-export type CSSClassesMap = Record<PropertyHash, CSSClasses>;
-export type CSSClassesMapBySlot<Slots extends string | number> = Record<Slots, CSSClassesMap>;
+  k?: CSSBucketEntry[];
+  // at-rules (@support, @layer)
+  t?: CSSBucketEntry[];
+  // @media rules
+  m?: CSSBucketEntry[];
+};
 
-export type CSSRulesByBucket = Partial<Record<StyleBucketName, string[]>>;
+export type CSSBucketEntry = string | [string, Record<string, unknown>];
 
 export type StylesBySlots<Slots extends string | number> = Record<Slots, GriffelStyle>;
 
diff --git a/packages/devtools/CHANGELOG.json b/packages/devtools/CHANGELOG.json
index a388d9a..46e036d 100644
--- a/packages/devtools/CHANGELOG.json
+++ b/packages/devtools/CHANGELOG.json
@@ -1,6 +1,36 @@
 {
   "name": "@griffel/devtools",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 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.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..bfdab80 100644
--- a/packages/devtools/CHANGELOG.md
+++ b/packages/devtools/CHANGELOG.md
@@ -1,11 +1,27 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
 ## 0.0.1
 
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- Bump @griffel/react to v1.2.1
+
+## 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..0b2c78b 100644
--- a/packages/devtools/package.json
+++ b/packages/devtools/package.json
@@ -10,7 +10,8 @@
   },
   "sideEffects": false,
   "dependencies": {
-    "@griffel/react": "^1.1.0",
+    "@emotion/hash": "^0.8.0",
+    "@griffel/react": "^1.2.1",
     "js-beautify": "^1.14.0",
     "prism-react-renderer": "1.2.1",
     "react-dom": ">=16.8.0 <18.0.0",
diff --git a/packages/devtools/src/FlattenView.tsx b/packages/devtools/src/FlattenView.tsx
index c60cfbd..278abff 100644
--- a/packages/devtools/src/FlattenView.tsx
+++ b/packages/devtools/src/FlattenView.tsx
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import hash from '@emotion/hash';
 import { makeStaticStyles, makeStyles, shorthands } from '@griffel/react';
 
 import { SlotCSSRules } from './SlotCSSRules';
@@ -19,6 +20,11 @@ const useStyles = makeStyles({
     columnGap: '4px',
     fontFamily: 'system-ui',
     fontSize: '12px',
+
+    position: 'sticky',
+    top: 0,
+    zIndex: 1,
+    backgroundColor: tokens.background,
   },
   input: {
     color: 'inherit',
@@ -92,9 +98,10 @@ export const FlattenView: React.FC<FlattenViewProps> = props => {
       </div>
       <div className={classes.rules}>
         <ViewContext.Provider value={contextValue}>
-          {filteredSlots.map(({ slot, rules }) => (
-            <SlotCSSRules key={slot} slot={slot} atomicRules={rules} />
-          ))}
+          {filteredSlots.map(({ slot, rules }) => {
+            const key = slot + rules.map(rule => rule.cssRule).join('');
+            return <SlotCSSRules key={key} slot={slot} atomicRules={rules} />;
+          })}
         </ViewContext.Provider>
       </div>
     </div>
diff --git a/packages/devtools/src/getMonolithicCSSRules.ts b/packages/devtools/src/getMonolithicCSSRules.ts
index ca4fcc6..be43cbb 100644
--- a/packages/devtools/src/getMonolithicCSSRules.ts
+++ b/packages/devtools/src/getMonolithicCSSRules.ts
@@ -1,16 +1,28 @@
 import * as stylis from 'stylis';
 import type { AtomicRules, MonolithicAtRules, MonolithicRules, RuleDetail } from './types';
 
+const griffelClassNameRegex = /^\.f[0-9a-z]+$/;
+
 function parseRuleElement(element: stylis.Element, overriddenBy?: string) {
   // example of `value`: `.f3xbvq9:hover`
   // `children` contains all CSS rules under the `value` selector
   const { value: classNameSelector, children } = element;
 
   if (Array.isArray(children)) {
-    const selector = stylis.tokenize(classNameSelector).slice(1).join('');
+    let className: string | undefined;
+    const selector = stylis
+      .tokenize(classNameSelector)
+      .filter(token => {
+        if (griffelClassNameRegex.test(token)) {
+          className = token.slice(1);
+          return false;
+        }
+        return true;
+      })
+      .join('');
     const rules: RuleDetail[] = children.map(child => ({
       css: child.value,
-      className: stylis.tokenize(classNameSelector)[0].slice(1),
+      className: className ?? stylis.tokenize(classNameSelector)[0].slice(1),
       overriddenBy,
     }));
     return {
diff --git a/packages/devtools/src/stories/FlattenView.stories.tsx b/packages/devtools/src/stories/FlattenView.stories.tsx
index 7949b18..864e295 100644
--- a/packages/devtools/src/stories/FlattenView.stories.tsx
+++ b/packages/devtools/src/stories/FlattenView.stories.tsx
@@ -79,7 +79,7 @@ const debugResultRoot: DebugResult = {
         f7wpa5l: '@media screen and (max-width: 992px){.f7wpa5l:hover{color:red;}}',
         fy4xd57: '@media screen and (max-width: 992px){.fy4xd57:hover{background-color:blue;}}',
       },
-      slot: 'primaryRed',
+      slot: 'primary',
     },
   ],
   debugClassNames: [
@@ -105,6 +105,8 @@ export const Default: Story<{ theme: 'dark' | 'light' }> = ({ theme }) => {
         ...tokens,
         border: '3px solid gray',
         width: 400,
+        height: 600,
+        overflowY: 'auto',
       }}
     >
       <FlattenView debugResultRoot={debugResultRoot} />
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/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index f353207..0037348 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -7,7 +7,7 @@ ESLint plugin to follow best practices and anticipate common mistakes when writi
 ```bash
 yarn add --dev @griffel/eslint-plugin
 # or
-npm install --dev @griffel/eslint-plugin
+npm install --save-dev @griffel/eslint-plugin
 ```
 
 ## Usage
diff --git a/packages/jest-serializer/CHANGELOG.json b/packages/jest-serializer/CHANGELOG.json
index 2331773..0123253 100644
--- a/packages/jest-serializer/CHANGELOG.json
+++ b/packages/jest-serializer/CHANGELOG.json
@@ -1,6 +1,36 @@
 {
   "name": "@griffel/jest-serializer",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/jest-serializer_v1.0.7",
+      "version": "1.0.7",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/jest-serializer",
+            "comment": "Bump @griffel/core to v1.4.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..51c14e8 100644
--- a/packages/jest-serializer/CHANGELOG.md
+++ b/packages/jest-serializer/CHANGELOG.md
@@ -1,9 +1,25 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.0.7
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.1
+
+## 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/README.md b/packages/jest-serializer/README.md
index b073d06..6ecb3da 100644
--- a/packages/jest-serializer/README.md
+++ b/packages/jest-serializer/README.md
@@ -9,7 +9,7 @@ Add it as a dependency:
 ```bash
 yarn add --dev @griffel/jest-serializer
 # or
-npm install --dev @griffel/jest-serializer
+npm install --save-dev @griffel/jest-serializer
 ```
 
 Update your `jest.config.js` (or appropriate config location) to include a `snapshotSerializers` array that references this package:
diff --git a/packages/jest-serializer/package.json b/packages/jest-serializer/package.json
index 15a3c6a..626fa65 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.7",
   "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.1"
   }
 }
diff --git a/packages/react/CHANGELOG.json b/packages/react/CHANGELOG.json
index 2b5bae7..f7454e0 100644
--- a/packages/react/CHANGELOG.json
+++ b/packages/react/CHANGELOG.json
@@ -1,6 +1,62 @@
 {
   "name": "@griffel/react",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/react_v1.2.1",
+      "version": "1.2.1",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/react",
+            "comment": "Bump @griffel/core to v1.4.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..70cf88d 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,9 +1,32 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.2.1
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.4.1
+
+## 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/README.md b/packages/react/README.md
index 5040e64..8cdf9aa 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -175,6 +175,36 @@ const useClasses = makeStyles({
 });
 ```
 
+### `shorthands.gridArea`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.gridArea('auto'),
+    ...shorthands.gridArea('first', 'second'),
+    ...shorthands.gridArea(2, 4, 'span footer'),
+    ...shorthands.gridArea(2, 4, 1, 3),
+  },
+});
+```
+
+### `shorthands.inset`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.inset('10px'),
+    ...shorthands.inset('10px', '5px'),
+    ...shorthands.inset('2px', '4px', '8px'),
+    ...shorthands.inset('1px', 0, '3px', '4px'),
+  },
+});
+```
+
 ### `shorthands.margin`
 
 ```js
@@ -335,7 +365,7 @@ const useClasses = makeStyles({
 
     '@media screen and (max-width: 992px)': { color: 'orange' },
     '@supports (display: grid)': { color: 'red' },
-    '@layer utility': { marginBottom: '1em' }
+    '@layer utility': { marginBottom: '1em' },
   },
 });
 ```
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..4295bb4 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.1",
   "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.1",
     "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..5cdceae 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -1,5 +1,5 @@
 export { shorthands, mergeClasses, createDOMRenderer } from '@griffel/core';
-export type { GriffelStyle } from '@griffel/core';
+export type { GriffelStyle, CreateDOMRendererOptions } from '@griffel/core';
 
 export { makeStyles } from './makeStyles';
 export { makeStaticStyles } from './makeStaticStyles';
@@ -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 946aab4..02ffe48 100644
--- a/packages/react/src/renderToStyleElements.ts
+++ b/packages/react/src/renderToStyleElements.ts
@@ -1,8 +1,6 @@
 import { styleBucketOrdering } from '@griffel/core';
 import * as React from 'react';
-import type { GriffelRenderer, StyleBucketName } from '@griffel/core';
-
-type CSSRulesGroupedByStyleBucket = Record<StyleBucketName, string[]>;
+import type { GriffelRenderer } from '@griffel/core';
 
 /**
  * This method returns a list of <style> React elements with the rendered CSS. This is useful for Server-Side rendering.
@@ -10,37 +8,45 @@ type CSSRulesGroupedByStyleBucket = Record<StyleBucketName, string[]>;
  * @public
  */
 export function renderToStyleElements(renderer: GriffelRenderer): React.ReactElement[] {
-  const styles = styleBucketOrdering.reduce<CSSRulesGroupedByStyleBucket>((acc, bucketName) => {
-    return { ...acc, [bucketName]: [] };
-  }, {} as CSSRulesGroupedByStyleBucket);
+  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'];
 
-  // eslint-disable-next-line guard-for-in
-  for (const cssRule in renderer.insertionCache) {
-    const bucketName: StyleBucketName = renderer.insertionCache[cssRule];
+      if (mediaA && mediaB) {
+        return renderer.compareMediaQueries(mediaA, mediaB);
+      }
 
-    styles[bucketName].push(cssRule);
-  }
+      if (mediaA || mediaB) {
+        return mediaA ? 1 : -1;
+      }
 
-  return (Object.keys(styles) as StyleBucketName[])
-    .map(bucketName => {
-      const cssRules = styles[bucketName].join('');
+      return 0;
+    });
 
-      // We don't want to create empty style elements
-      if (cssRules.length === 0) {
+  return stylesheets
+    .map(stylesheet => {
+      const cssRules = stylesheet.cssRules();
+      // don't want to create any empty style elements
+      if (!cssRules.length) {
         return null;
       }
 
       return React.createElement('style', {
-        key: bucketName,
+        key: stylesheet.bucketName,
 
         // TODO: support "nonce"
         // ...renderer.styleNodeAttributes,
-
-        'data-make-styles-bucket': bucketName || 'default',
-        'data-make-styles-rehydration': true,
+        ...stylesheet.elementAttributes,
+        'data-make-styles-rehydration': 'true',
 
         dangerouslySetInnerHTML: {
-          __html: cssRules,
+          __html: cssRules.join(''),
         },
       });
     })
diff --git a/packages/webpack-extraction-plugin/.eslintrc.json b/packages/webpack-extraction-plugin/.eslintrc.json
new file mode 100644
index 0000000..712601a
--- /dev/null
+++ b/packages/webpack-extraction-plugin/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+  "extends": ["../../.eslintrc.json"],
+  "ignorePatterns": ["!**/*", "__fixtures__/**/*/output.ts"],
+  "overrides": [
+    {
+      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.ts", "*.tsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.js", "*.jsx"],
+      "rules": {}
+    }
+  ]
+}
diff --git a/packages/webpack-extraction-plugin/CHANGELOG.json b/packages/webpack-extraction-plugin/CHANGELOG.json
new file mode 100644
index 0000000..242ca5e
--- /dev/null
+++ b/packages/webpack-extraction-plugin/CHANGELOG.json
@@ -0,0 +1,56 @@
+{
+  "name": "@griffel/webpack-extraction-plugin",
+  "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 15:47:33 GMT",
+      "tag": "@griffel/webpack-extraction-plugin_v0.1.1",
+      "version": "0.1.1",
+      "comments": {
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/webpack-extraction-plugin",
+            "commit": "feffd3c775fcd4f9c4af34ed845ef5e1fc016a27",
+            "comment": "fix: improve getElementReference() to handle multiple childen"
+          }
+        ]
+      }
+    },
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/webpack-extraction-plugin_v0.1.0",
+      "version": "0.1.0",
+      "comments": {
+        "minor": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/webpack-extraction-plugin",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d",
+            "comment": "chore: initial release"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-extraction-plugin",
+            "comment": "Bump @griffel/core to v1.4.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..e6735a2
--- /dev/null
+++ b/packages/webpack-extraction-plugin/CHANGELOG.md
@@ -0,0 +1,30 @@
+# Change Log - @griffel/webpack-extraction-plugin
+
+This log was last generated on Tue, 28 Jun 2022 15:47:33 GMT and should not be manually modified.
+
+<!-- Start content -->
+
+## 0.1.1
+
+Tue, 28 Jun 2022 15:47:33 GMT
+
+### Patches
+
+- fix: improve getElementReference() to handle multiple childen (olfedias@microsoft.com)
+
+## 0.1.0
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Minor changes
+
+- chore: initial release (olfedias@microsoft.com)
+- Bump @griffel/core to v1.4.1
+
+## 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/README.md b/packages/webpack-extraction-plugin/README.md
new file mode 100644
index 0000000..928ab7f
--- /dev/null
+++ b/packages/webpack-extraction-plugin/README.md
@@ -0,0 +1,98 @@
+# Webpack plugin to perform CSS extraction in Griffel
+
+> ⚠️ **Note** This package is experimental, API may change.
+
+A plugin for Webpack 5 that performs CSS extraction for [`@griffel/react`](../react).
+
+## Install
+
+```bash
+yarn add --dev @griffel/webpack-extraction-plugin
+# or
+npm install --save-dev @griffel/webpack-extraction-plugin
+```
+
+## When to use it?
+
+It's designed to be used only in applications.
+
+## How it works?
+
+This plugin relies on assets transformed by [`@griffel/babel-preset`](../babel-preset) or [`@griffel/webpack-loader`](../webpack-loader). Usage of these utilities is a **prerequisite**.
+
+The plugin transforms code to remove generated CSS from JavaScript files and create CSS assets.
+
+_Currently, all CSS rules will be extracted to a single CSS file i.e. [code splitting](https://webpack.js.org/guides/code-splitting/) for extracted CSS **will not work**._
+
+## Usage
+
+Webpack documentation:
+
+- [Loaders](https://webpack.js.org/loaders/)
+- [Plugins](https://webpack.js.org/concepts/plugins/)
+
+> Please configure [`@griffel/webpack-loader`](../webpack-loader) first.
+
+Within your Webpack configuration object, you'll need to add the loader and the plugin from `@griffel/webpack-extraction-plugin` like so:
+
+```js
+const { GriffelCSSExtractionPlugin } = require('@griffel/webpack-extraction-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.(js|ts|tsx)$/,
+        // Apply "exclude" only if your dependencies **do not use** Griffel
+        // exclude: /node_modules/,
+        use: {
+          loader: GriffelCSSExtractionPlugin.loader,
+        },
+      },
+      // Add "@griffel/webpack-loader" if you use Griffel directly in your project
+      {
+        test: /\.(ts|tsx)$/,
+        exclude: /node_modules/,
+        use: {
+          loader: '@griffel/webpack-loader',
+        },
+      },
+      // "css-loader" is required to handle produced CSS assets by Griffel
+      // you can use "style-loader" or "MiniCssExtractPlugin.loader" to handle CSS insertion
+      {
+        test: /\.css$/,
+        use: [MiniCssExtractPlugin.loader, 'css-loader'],
+      },
+    ],
+  },
+  plugins: [new MiniCssExtractPlugin(), new GriffelCSSExtractionPlugin()],
+};
+```
+
+- `mini-css-extract-plugin` is not mandatory and configured there for example, you can use `style-loader` or other tooling to inject CSS on a page
+
+For better performance (to process less files) consider to use `include` for `GriffelCSSExtractionPlugin.loader`:
+
+```js
+const { GriffelCSSExtractionPlugin } = require('@griffel/webpack-extraction-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.(js|ts|tsx)$/,
+        include: [
+          path.resolve(__dirname, 'components'),
+          /\/node_modules\/@fluentui\/,
+          // see https://webpack.js.org/configuration/module/#condition
+        ],
+        use: {
+          loader: GriffelCSSExtractionPlugin.loader,
+        },
+      },
+    ],
+  },
+};
+```
diff --git a/packages/webpack-extraction-plugin/__fixtures__/babel/basic/code.ts b/packages/webpack-extraction-plugin/__fixtures__/babel/basic/code.ts
new file mode 100644
index 0000000..19dad50
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/babel/basic/code.ts
@@ -0,0 +1,22 @@
+import { __styles } from '@griffel/react';
+
+export const styles = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+      uwmqm3: ['fycuoez', 'f8wuabp'],
+    },
+    icon: {
+      De3pzq: 'fcnqdeg',
+      Bi91k9c: 'faf35ka',
+    },
+  },
+  {
+    d: [
+      '.fe3e8s9{color:red;}',
+      '.fycuoez{padding-left:4px;}',
+      '.f8wuabp{padding-right:4px;}',
+      '.fcnqdeg{background-color:green;}',
+    ],
+  },
+);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/babel/basic/output.ts b/packages/webpack-extraction-plugin/__fixtures__/babel/basic/output.ts
new file mode 100644
index 0000000..2a6fcfd
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/babel/basic/output.ts
@@ -0,0 +1,11 @@
+import { __styles, __css } from '@griffel/react';
+export const styles = __css({
+  root: {
+    sj55zd: 'fe3e8s9',
+    uwmqm3: ['fycuoez', 'f8wuabp'],
+  },
+  icon: {
+    De3pzq: 'fcnqdeg',
+    Bi91k9c: 'faf35ka',
+  },
+});
diff --git a/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/code.ts b/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/code.ts
new file mode 100644
index 0000000..0b4f067
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/code.ts
@@ -0,0 +1,23 @@
+import { __styles } from '@griffel/react';
+
+export const stylesA = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+
+export const stylesB = __styles(
+  {
+    root: {
+      De3pzq: 'fcnqdeg',
+    },
+  },
+  {
+    d: ['.fcnqdeg{background-color:green;}'],
+  },
+);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/output.ts b/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/output.ts
new file mode 100644
index 0000000..9669953
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/babel/multiple/output.ts
@@ -0,0 +1,11 @@
+import { __styles, __css } from '@griffel/react';
+export const stylesA = __css({
+  root: {
+    sj55zd: 'fe3e8s9',
+  },
+});
+export const stylesB = __css({
+  root: {
+    De3pzq: 'fcnqdeg',
+  },
+});
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/code.ts
new file mode 100644
index 0000000..3c20101
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/code.ts
@@ -0,0 +1,24 @@
+import { __styles } from '@griffel/react';
+
+const styles = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+      uwmqm3: ['fycuoez', 'f8wuabp'],
+    },
+    icon: {
+      De3pzq: 'fcnqdeg',
+      Bi91k9c: 'faf35ka',
+    },
+  },
+  {
+    d: [
+      '.fe3e8s9{color:red;}',
+      '.fycuoez{padding-left:4px;}',
+      '.f8wuabp{padding-right:4px;}',
+      '.fcnqdeg{background-color:green;}',
+    ],
+  },
+);
+
+console.log(styles);
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/__fixtures__/webpack/basic-rules/output.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/output.ts
new file mode 100644
index 0000000..5c87bf6
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/basic-rules/output.ts
@@ -0,0 +1,16 @@
+import { __styles, __css } from '@griffel/react';
+
+const styles = __css({
+  root: {
+    sj55zd: 'fe3e8s9',
+    uwmqm3: ['fycuoez', 'f8wuabp'],
+  },
+  icon: {
+    De3pzq: 'fcnqdeg',
+    Bi91k9c: 'faf35ka',
+  },
+});
+
+console.log(styles);
+
+import 'griffel.css!=!../../../virtual-loader/index.js!../../../virtual-loader/griffel.css?style=.fe3e8s9%7Bcolor%3Ared%3B%7D%0A.fycuoez%7Bpadding-left%3A4px%3B%7D%0A.f8wuabp%7Bpadding-right%3A4px%3B%7D%0A.fcnqdeg%7Bbackground-color%3Agreen%3B%7D';
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/code.ts
new file mode 100644
index 0000000..0be9cd8
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/code.ts
@@ -0,0 +1,14 @@
+import { __styles } from '@griffel/react';
+
+const styles = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+
+console.log(styles);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/fs.json
new file mode 100644
index 0000000..7a3911c
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.e44646c584b8724e9528.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/output.css
new file mode 100644
index 0000000..b0a2f30
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/config-name/output.css
@@ -0,0 +1,3 @@
+.fe3e8s9 {
+  color: red;
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/code.ts
new file mode 100644
index 0000000..0b4f067
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/code.ts
@@ -0,0 +1,23 @@
+import { __styles } from '@griffel/react';
+
+export const stylesA = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+
+export const stylesB = __styles(
+  {
+    root: {
+      De3pzq: 'fcnqdeg',
+    },
+  },
+  {
+    d: ['.fcnqdeg{background-color:green;}'],
+  },
+);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.css
new file mode 100644
index 0000000..1bbd349
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.css
@@ -0,0 +1,6 @@
+.fe3e8s9 {
+  color: red;
+}
+.fcnqdeg {
+  background-color: green;
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.ts
new file mode 100644
index 0000000..42aeb0d
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/multiple/output.ts
@@ -0,0 +1,13 @@
+import { __styles, __css } from '@griffel/react';
+export const stylesA = __css({
+  root: {
+    sj55zd: 'fe3e8s9',
+  },
+});
+export const stylesB = __css({
+  root: {
+    De3pzq: 'fcnqdeg',
+  },
+});
+
+import 'griffel.css!=!../../../virtual-loader/index.js!../../../virtual-loader/griffel.css?style=.fe3e8s9%7Bcolor%3Ared%3B%7D%0A.fcnqdeg%7Bbackground-color%3Agreen%3B%7D';
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/code.ts
new file mode 100644
index 0000000..b1c9aca
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/code.ts
@@ -0,0 +1,27 @@
+import { __styles } from '@griffel/react';
+
+const styles1 = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+      uwmqm3: ['fycuoez', 'f8wuabp'],
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}', '.fcnqdeg{background-color:green;}'],
+  },
+);
+
+const styles2 = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+      uwmqm3: ['fycuoez', 'f8wuabp'],
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}', '.fcnqdeg{background-color:green;}'],
+  },
+);
+
+console.log(styles1, styles2);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/output.css
new file mode 100644
index 0000000..1bbd349
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/rules-deduplication/output.css
@@ -0,0 +1,6 @@
+.fe3e8s9 {
+  color: red;
+}
+.fcnqdeg {
+  background-color: green;
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/code.ts
new file mode 100644
index 0000000..d60249f
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/code.ts
@@ -0,0 +1,27 @@
+import { __styles } from '@griffel/react';
+
+const styles = __styles(
+  {},
+  // Classes in this test are intentionally not realistic to simplify snapshots
+  {
+    d: ['.color-red { color: red; }', '.animation-name { animation-name: foo; }'],
+    l: ['.color-orange:link { color: orange; }'],
+    v: ['.color-purple:visited { color: purple; }'],
+    w: ['.color-pink:focus-within { color: pink; }'],
+    f: ['.color-blue:focus { color: blue; }'],
+    i: ['.color-light-blue:focus-visible { color: salmon; }'],
+    h: ['.color-yellow:hover { color: yellow; }'],
+    a: ['.color-black:active { color: black; }'],
+    k: ['@keyframes foo { from{ transform:rotate(0deg); } to { transform:rotate(360deg); } }'],
+    m: [
+      [
+        '@media (forced-colors: active) { .color-magenta { color: magenta; } }',
+        {
+          m: '(forced-colors: active)',
+        },
+      ],
+    ],
+  },
+);
+
+console.log(styles);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/output.css
new file mode 100644
index 0000000..432f7ce
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/style-buckets/output.css
@@ -0,0 +1,40 @@
+.color-red {
+  color: red;
+}
+.animation-name {
+  animation-name: foo;
+}
+.color-orange:link {
+  color: orange;
+}
+.color-purple:visited {
+  color: purple;
+}
+.color-pink:focus-within {
+  color: pink;
+}
+.color-blue:focus {
+  color: blue;
+}
+.color-light-blue:focus-visible {
+  color: salmon;
+}
+.color-yellow:hover {
+  color: yellow;
+}
+.color-black:active {
+  color: black;
+}
+@keyframes foo {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+@media (forced-colors: active) {
+  .color-magenta {
+    color: magenta;
+  }
+}
diff --git a/packages/webpack-extraction-plugin/jest.config.js b/packages/webpack-extraction-plugin/jest.config.js
new file mode 100644
index 0000000..cd3d208
--- /dev/null
+++ b/packages/webpack-extraction-plugin/jest.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+  displayName: 'webpack-extraction-plugin',
+  preset: '../../jest.preset.js',
+  globals: {
+    'ts-jest': {
+      tsconfig: '<rootDir>/tsconfig.spec.json',
+    },
+  },
+  testEnvironment: 'node',
+  transform: {
+    '^.+\\.[tj]sx?$': 'ts-jest',
+  },
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
+  coverageDirectory: '../../coverage/packages/webpack-loader',
+};
diff --git a/packages/webpack-extraction-plugin/package.json b/packages/webpack-extraction-plugin/package.json
new file mode 100644
index 0000000..eb01591
--- /dev/null
+++ b/packages/webpack-extraction-plugin/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "@griffel/webpack-extraction-plugin",
+  "version": "0.1.1",
+  "description": "Webpack plugin that performs CSS extraction for Griffel",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/microsoft/griffel"
+  },
+  "dependencies": {
+    "@babel/core": "^7.12.13",
+    "@babel/helper-plugin-utils": "^7.12.13",
+    "loader-utils": "^2.0.0",
+    "schema-utils": "^3.1.1",
+    "stylis": "^4.0.13",
+    "tslib": "^2.1.0"
+  },
+  "peerDependencies": {
+    "@griffel/core": "^1.4.1",
+    "webpack": "^5"
+  }
+}
diff --git a/packages/webpack-extraction-plugin/project.json b/packages/webpack-extraction-plugin/project.json
new file mode 100644
index 0000000..13cd794
--- /dev/null
+++ b/packages/webpack-extraction-plugin/project.json
@@ -0,0 +1,54 @@
+{
+  "root": "packages/webpack-extraction-plugin",
+  "sourceRoot": "packages/webpack-extraction-plugin/src",
+  "projectType": "library",
+  "targets": {
+    "lint": {
+      "executor": "@nrwl/linter:eslint",
+      "outputs": ["{options.outputFile}"],
+      "options": {
+        "lintFilePatterns": ["packages/webpack-extraction-plugin/**/*.ts"]
+      }
+    },
+    "test": {
+      "executor": "@nrwl/jest:jest",
+      "outputs": ["coverage/packages/webpack-extraction-plugin"],
+      "options": {
+        "jestConfig": "packages/webpack-extraction-plugin/jest.config.js",
+        "passWithNoTests": true
+      }
+    },
+    "build": {
+      "executor": "@nrwl/node:package",
+      "outputs": ["{options.outputPath}"],
+      "options": {
+        "outputPath": "dist/packages/webpack-extraction-plugin",
+        "tsConfig": "packages/webpack-extraction-plugin/tsconfig.lib.json",
+        "packageJson": "packages/webpack-extraction-plugin/package.json",
+        "main": "packages/webpack-extraction-plugin/src/index.ts",
+        "assets": [
+          "packages/webpack-extraction-plugin/README.md",
+          {
+            "glob": "LICENSE.md",
+            "input": ".",
+            "output": "."
+          },
+          {
+            "glob": "*.{js,css}",
+            "input": "packages/webpack-extraction-plugin/virtual-loader",
+            "output": "./virtual-loader"
+          }
+        ]
+      }
+    },
+    "type-check": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "packages/webpack-extraction-plugin",
+        "commands": [{ "command": "tsc -b --pretty" }],
+        "outputPath": []
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
new file mode 100644
index 0000000..5d73692
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
@@ -0,0 +1,73 @@
+import { defaultCompareMediaQueries, GriffelRenderer } from '@griffel/core';
+import { Compilation } from 'webpack';
+import type { Compiler, sources } from 'webpack';
+
+import { sortCSSRules } from './sortCSSRules';
+
+type GriffelCSSExtractionPluginOptions = {
+  compareMediaQueries?: GriffelRenderer['compareMediaQueries'];
+};
+
+/**
+ * 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');
+
+  private readonly compareMediaQueries: NonNullable<GriffelCSSExtractionPluginOptions['compareMediaQueries']>;
+
+  constructor(options?: GriffelCSSExtractionPluginOptions) {
+    this.compareMediaQueries = options?.compareMediaQueries ?? defaultCompareMediaQueries;
+  }
+
+  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 = Object.entries(assets).find(([assetName]) => assetName.includes('griffel'));
+
+          if (!griffelAsset) {
+            return;
+          }
+
+          const [assetName, assetSource] = griffelAsset;
+
+          const unsortedCSSRules = getAssetSourceContents(assetSource);
+          const sortedCSSRules = sortCSSRules(unsortedCSSRules, this.compareMediaQueries);
+
+          compilation.updateAsset(assetName, new compiler.webpack.sources.RawSource(sortedCSSRules));
+        },
+      );
+    });
+  }
+}
diff --git a/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts
new file mode 100644
index 0000000..5da2e04
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts
@@ -0,0 +1,113 @@
+import { NodePath, PluginObj, PluginPass, types as t } from '@babel/core';
+import { declare } from '@babel/helper-plugin-utils';
+import { CSSRulesByBucket, normalizeCSSBucketEntry } from '@griffel/core';
+
+type StripRuntimeBabelPluginOptions = never;
+
+type StripRuntimeBabelPluginState = PluginPass & {
+  cssRules?: string[];
+};
+
+export type StripRuntimeBabelPluginMetadata = {
+  cssRules: string[];
+};
+
+export const babelPluginStripGriffelRuntime = declare<
+  Partial<StripRuntimeBabelPluginOptions>,
+  PluginObj<StripRuntimeBabelPluginState>
+>(api => {
+  api.assertVersion(7);
+
+  return {
+    name: '@griffel/webpack-extraction-plugin/babel',
+    pre() {
+      this.cssRules = [];
+    },
+    post() {
+      (this.file.metadata as unknown as StripRuntimeBabelPluginMetadata).cssRules = this.cssRules!;
+    },
+
+    visitor: {
+      Program: {
+        exit(path, state) {
+          path.traverse({
+            ImportSpecifier(path) {
+              const importedPath = path.get('imported');
+
+              if (!importedPath.isIdentifier({ name: '__styles' })) {
+                return;
+              }
+
+              const declarationPath = path.findParent(p =>
+                p.isImportDeclaration(),
+              ) as NodePath<t.ImportDeclaration> | null;
+
+              if (declarationPath === null) {
+                throw path.buildCodeFrameError(
+                  [
+                    'Failed to find "ImportDeclaration" path for an "ImportSpecifier".',
+                    'Please report a bug (https://github.com/microsoft/griffel/issues) if this error happens',
+                  ].join(' '),
+                );
+              }
+
+              declarationPath.pushContainer('specifiers', t.identifier('__css'));
+            },
+
+            /**
+             * Visits all call expressions (__styles function calls):
+             * - replaces "__styles" calls "__css"
+             * - removes CSS rules from "__styles" calls
+             */
+            CallExpression(path) {
+              const calleePath = path.get('callee');
+
+              if (!calleePath.isIdentifier({ name: '__styles' })) {
+                return;
+              }
+
+              calleePath.replaceWith(t.identifier('__css'));
+
+              const argumentPaths = path.get('arguments');
+
+              if (argumentPaths.length !== 2) {
+                throw calleePath.buildCodeFrameError(
+                  [
+                    '"__styles" function call should have exactly two arguments.',
+                    'Please report a bug (https://github.com/microsoft/griffel/issues) if this error happens',
+                  ].join(' '),
+                );
+              }
+
+              // Returns the styles as a JavaScript object
+              const evaluationResult = argumentPaths[1].evaluate();
+
+              if (!evaluationResult.confident) {
+                throw argumentPaths[1].buildCodeFrameError(
+                  [
+                    'Failed to evaluate CSS rules from "__styles" call.',
+                    'Please report a bug (https://github.com/microsoft/griffel/issues) if this error happens',
+                  ].join(' '),
+                );
+              }
+
+              const cssRulesByBucket = evaluationResult.value as CSSRulesByBucket;
+
+              Object.values(cssRulesByBucket).forEach(cssBucketEntries => {
+                const cssRules = cssBucketEntries.map(cssBucketEntry => {
+                  const [cssRule] = normalizeCSSBucketEntry(cssBucketEntry);
+
+                  return cssRule;
+                });
+
+                state.cssRules!.push(...cssRules);
+              });
+
+              argumentPaths[1].remove();
+            },
+          });
+        },
+      },
+    },
+  };
+});
diff --git a/packages/webpack-extraction-plugin/src/index.ts b/packages/webpack-extraction-plugin/src/index.ts
new file mode 100644
index 0000000..6e1e6f2
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/index.ts
@@ -0,0 +1,1 @@
+export { GriffelCSSExtractionPlugin } from './GriffelCSSExtractionPlugin';
diff --git a/packages/webpack-extraction-plugin/src/schema.ts b/packages/webpack-extraction-plugin/src/schema.ts
new file mode 100644
index 0000000..3b8433c
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/schema.ts
@@ -0,0 +1,9 @@
+import type { JSONSchema7 } from 'json-schema';
+
+export const configSchema: JSONSchema7 = {
+  $schema: 'http://json-schema.org/schema',
+  $id: 'webpackLoader-options',
+
+  properties: {},
+  additionalProperties: false,
+};
diff --git a/packages/webpack-extraction-plugin/src/sortCSSRules.ts b/packages/webpack-extraction-plugin/src/sortCSSRules.ts
new file mode 100644
index 0000000..7b2334e
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/sortCSSRules.ts
@@ -0,0 +1,85 @@
+import { getStyleBucketName, GriffelRenderer, StyleBucketName, styleBucketOrdering } from '@griffel/core';
+import { COMMENT, compile, Element, KEYFRAMES, MEDIA, RULESET, serialize, stringify, SUPPORTS, tokenize } from 'stylis';
+
+export function getSelectorFromElement(element: Element) {
+  return tokenize(element.value).slice(1).join('');
+}
+
+export function getElementMetadata(element: Element): string {
+  if (element.type === MEDIA) {
+    return element.value.replace(/^@media/, '').trim();
+  }
+
+  return '';
+}
+
+export function getElementReference(element: Element, suffix = ''): string {
+  if (element.type === RULESET || element.type === KEYFRAMES) {
+    return element.value + suffix;
+  }
+
+  if (Array.isArray(element.children)) {
+    return element.value + '[' + element.children.map(child => getElementReference(child, suffix)).join(',') + ']';
+  }
+
+  function removeRootProperty(element: Element): unknown {
+    return {
+      ...element,
+      children: Array.isArray(element.children)
+        ? element.children.map(child => removeRootProperty(child))
+        : element.children,
+      root: undefined,
+      parent: undefined,
+    };
+  }
+
+  throw new Error(
+    [
+      'getElementReference(): An unhandled case, please report if it happens and provide debug information about an element:',
+      JSON.stringify(removeRootProperty(element), null, 2),
+    ].join('\n'),
+  );
+}
+
+export function getStyleBucketNameFromElement(element: Element): StyleBucketName {
+  if (element.type === KEYFRAMES) {
+    return 'k';
+  }
+
+  return getStyleBucketName(
+    getSelectorFromElement(element),
+    element.type === '@layer' ? element.value : '',
+    element.type === MEDIA ? element.value : '',
+    element.type === SUPPORTS ? element.value : '',
+  );
+}
+
+export function sortCSSRules(css: string, compareMediaQueries: GriffelRenderer['compareMediaQueries']): string {
+  const childElements = compile(css)
+    // Remove top level comments as it is unclear how to sort them
+    .filter(element => element.type !== COMMENT)
+    .map(element => ({
+      ...element,
+      bucketName: getStyleBucketNameFromElement(element),
+      metadata: getElementMetadata(element),
+      reference: getElementReference(element),
+    }));
+  const uniqueElements = childElements.reduce<Record<string, typeof childElements[0]>>((acc, element) => {
+    acc[element.reference] = element;
+
+    return acc;
+  }, {});
+  const sortedElements = Object.values(uniqueElements).sort((elementA, elementB) => {
+    if (elementA.bucketName === 'm' && elementB.bucketName === 'm') {
+      return compareMediaQueries(elementA.metadata, elementB.metadata);
+    }
+
+    if (elementA.bucketName === elementB.bucketName) {
+      return 0;
+    }
+
+    return styleBucketOrdering.indexOf(elementA.bucketName) - styleBucketOrdering.indexOf(elementB.bucketName);
+  });
+
+  return serialize(sortedElements, stringify);
+}
diff --git a/packages/webpack-extraction-plugin/src/transformSync.ts b/packages/webpack-extraction-plugin/src/transformSync.ts
new file mode 100644
index 0000000..ed142d1
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/transformSync.ts
@@ -0,0 +1,57 @@
+import * as Babel from '@babel/core';
+import { babelPluginStripGriffelRuntime, StripRuntimeBabelPluginMetadata } from './babelPluginStripGriffelRuntime';
+
+export type TransformOptions = {
+  filename: string;
+
+  inputSourceMap: Babel.TransformOptions['inputSourceMap'];
+  enableSourceMaps: boolean;
+};
+
+export type TransformResult = {
+  code: string;
+  cssRules: string[] | undefined;
+  sourceMap: NonNullable<Babel.BabelFileResult['map']> | undefined;
+};
+
+/**
+ * Transforms passed source code with Babel, uses user's config for parsing, but ignores it for transforms.
+ */
+export function transformSync(sourceCode: string, options: TransformOptions): TransformResult {
+  // Parse the code first so Babel will use user's babel config for parsing
+  // During transforms we don't want to use user's config
+  const babelAST = Babel.parseSync(sourceCode, {
+    caller: { name: 'griffel' },
+
+    filename: options.filename,
+    inputSourceMap: options.inputSourceMap,
+    sourceMaps: options.enableSourceMaps,
+  });
+
+  if (babelAST === null) {
+    throw new Error(`Failed to create AST for "${options.filename}" due unknown Babel error...`);
+  }
+
+  const babelFileResult = Babel.transformFromAstSync(babelAST, sourceCode, {
+    // Ignore all user's configs and apply only our plugin
+    babelrc: false,
+    configFile: false,
+    plugins: [babelPluginStripGriffelRuntime],
+
+    filename: options.filename,
+
+    sourceMaps: options.enableSourceMaps,
+    sourceFileName: options.filename,
+    inputSourceMap: options.inputSourceMap,
+  });
+
+  if (babelFileResult === null) {
+    throw new Error(`Failed to transform "${options.filename}" due unknown Babel error...`);
+  }
+
+  return {
+    code: babelFileResult.code as string,
+    cssRules: (babelFileResult.metadata as unknown as StripRuntimeBabelPluginMetadata).cssRules,
+    sourceMap: babelFileResult.map === null ? undefined : babelFileResult.map,
+  };
+}
diff --git a/packages/webpack-extraction-plugin/src/webpackLoader.ts b/packages/webpack-extraction-plugin/src/webpackLoader.ts
new file mode 100644
index 0000000..7159712
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/webpackLoader.ts
@@ -0,0 +1,97 @@
+import { getOptions } from 'loader-utils';
+import * as path from 'path';
+import { validate } from 'schema-utils';
+import * as webpack from 'webpack';
+
+import { transformSync, TransformResult, TransformOptions } from './transformSync';
+import { configSchema } from './schema';
+
+type WebpackLoaderOptions = never;
+
+type WebpackLoaderParams = Parameters<webpack.LoaderDefinitionFunction<WebpackLoaderOptions>>;
+
+const virtualLoaderPath = path.resolve(__dirname, '..', 'virtual-loader', 'index.js');
+const resourcePath = path.resolve(__dirname, '..', 'virtual-loader', 'griffel.css');
+
+function toURIComponent(rule: string): string {
+  return encodeURIComponent(rule).replace(/!/g, '%21');
+}
+
+function shouldTransformSourceCode(sourceCode: string): boolean {
+  return sourceCode.indexOf('__styles') !== -1;
+}
+
+/**
+ * Webpack can also pass sourcemaps as a string, Babel accepts only objects.
+ * See https://github.com/babel/babel-loader/pull/889.
+ */
+function parseSourceMap(inputSourceMap: WebpackLoaderParams[1]): TransformOptions['inputSourceMap'] {
+  try {
+    if (typeof inputSourceMap === 'string') {
+      return JSON.parse(inputSourceMap) as TransformOptions['inputSourceMap'];
+    }
+
+    return inputSourceMap as TransformOptions['inputSourceMap'];
+  } catch (err) {
+    return undefined;
+  }
+}
+
+function webpackLoader(
+  this: webpack.LoaderContext<never>,
+  sourceCode: WebpackLoaderParams[0],
+  inputSourceMap: WebpackLoaderParams[1],
+) {
+  this.async();
+  // Loaders are cacheable by default, but in edge cases/bugs when caching does not work until it's specified:
+  // https://github.com/webpack/webpack/issues/14946
+  this.cacheable();
+
+  const options = getOptions(this) as WebpackLoaderOptions;
+
+  validate(configSchema, options, {
+    name: '@griffel/webpack-extraction-plugin/loader',
+    baseDataPath: 'options',
+  });
+
+  // Early return to handle cases when __styles() calls are not present, allows skipping expensive invocation of Babel
+  if (!shouldTransformSourceCode(sourceCode)) {
+    this.callback(null, sourceCode, inputSourceMap);
+    return;
+  }
+
+  let result: TransformResult | null = null;
+  let error: Error | null = null;
+
+  try {
+    result = transformSync(sourceCode, {
+      filename: path.relative(process.cwd(), this.resourcePath),
+
+      enableSourceMaps: this.sourceMap || false,
+      inputSourceMap: parseSourceMap(inputSourceMap),
+    });
+  } catch (err) {
+    error = err as Error;
+  }
+
+  if (result) {
+    if (result.cssRules) {
+      const request = `import ${JSON.stringify(
+        this.utils.contextify(
+          this.context || this.rootContext,
+          `griffel.css!=!${virtualLoaderPath}!${resourcePath}?style=${toURIComponent(result.cssRules.join('\n'))}`,
+        ),
+      )};`;
+
+      this.callback(null, `${result.code}\n\n${request};`, result.sourceMap);
+      return;
+    }
+
+    this.callback(null, result.code, result.sourceMap);
+    return;
+  }
+
+  this.callback(error);
+}
+
+export default webpackLoader;
diff --git a/packages/webpack-extraction-plugin/tsconfig.json b/packages/webpack-extraction-plugin/tsconfig.json
new file mode 100644
index 0000000..0639863
--- /dev/null
+++ b/packages/webpack-extraction-plugin/tsconfig.json
@@ -0,0 +1,24 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "lib": ["es2019", "dom", "DOM.Iterable"],
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "files": [],
+  "include": [],
+  "references": [
+    {
+      "path": "./tsconfig.lib.json"
+    },
+    {
+      "path": "./tsconfig.spec.json"
+    }
+  ]
+}
diff --git a/packages/webpack-extraction-plugin/tsconfig.lib.json b/packages/webpack-extraction-plugin/tsconfig.lib.json
new file mode 100644
index 0000000..fa6f95d
--- /dev/null
+++ b/packages/webpack-extraction-plugin/tsconfig.lib.json
@@ -0,0 +1,11 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "module": "commonjs",
+    "outDir": "../../dist/out-tsc",
+    "declaration": true,
+    "types": ["node", "environment"]
+  },
+  "exclude": ["__fixtures__/**/*/*.ts", "**/*.spec.ts", "**/*.test.ts"],
+  "include": ["**/*.ts", "virtual-loader/index.js"]
+}
diff --git a/packages/webpack-extraction-plugin/tsconfig.spec.json b/packages/webpack-extraction-plugin/tsconfig.spec.json
new file mode 100644
index 0000000..c8d8748
--- /dev/null
+++ b/packages/webpack-extraction-plugin/tsconfig.spec.json
@@ -0,0 +1,21 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "jsx": "react",
+    "outDir": "../../dist/out-tsc",
+    "module": "commonjs",
+    "types": ["jest", "node", "environment"]
+  },
+  "include": [
+    "__fixtures__/**/*/code.ts",
+    "**/*.test.ts",
+    "**/*.spec.ts",
+    "**/*.test.tsx",
+    "**/*.spec.tsx",
+    "**/*.test.js",
+    "**/*.spec.js",
+    "**/*.test.jsx",
+    "**/*.spec.jsx",
+    "**/*.d.ts"
+  ]
+}
diff --git a/packages/webpack-extraction-plugin/virtual-loader/griffel.css b/packages/webpack-extraction-plugin/virtual-loader/griffel.css
new file mode 100644
index 0000000..24590f7
--- /dev/null
+++ b/packages/webpack-extraction-plugin/virtual-loader/griffel.css
@@ -0,0 +1,2 @@
+/* This is a noop file for extracted CSS source to point to */
+/* Webpack requires a file to exist on disk for virtual source files */
diff --git a/packages/webpack-extraction-plugin/virtual-loader/index.js b/packages/webpack-extraction-plugin/virtual-loader/index.js
new file mode 100644
index 0000000..48d51b1
--- /dev/null
+++ b/packages/webpack-extraction-plugin/virtual-loader/index.js
@@ -0,0 +1,14 @@
+const { URLSearchParams } = require('url');
+
+/**
+ * @this {import('webpack').LoaderContext<unknown>}
+ * @return {String}
+ */
+function virtualLoader() {
+  const query = new URLSearchParams(this.resourceQuery);
+  const styleRule = query.get('style');
+
+  return styleRule ?? '';
+}
+
+module.exports = virtualLoader;
diff --git a/packages/webpack-loader/CHANGELOG.json b/packages/webpack-loader/CHANGELOG.json
index efcc416..af56b67 100644
--- a/packages/webpack-loader/CHANGELOG.json
+++ b/packages/webpack-loader/CHANGELOG.json
@@ -1,6 +1,48 @@
 {
   "name": "@griffel/webpack-loader",
   "entries": [
+    {
+      "date": "Tue, 28 Jun 2022 09:50:50 GMT",
+      "tag": "@griffel/webpack-loader_v2.0.8",
+      "version": "2.0.8",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/babel-preset to v1.3.3",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/react to v1.2.1",
+            "commit": "604926fc2fdd4596cd122b92b82a3f5e5d40d23d"
+          }
+        ]
+      }
+    },
+    {
+      "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..c41a993 100644
--- a/packages/webpack-loader/CHANGELOG.md
+++ b/packages/webpack-loader/CHANGELOG.md
@@ -1,9 +1,27 @@
 # 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 Tue, 28 Jun 2022 09:50:50 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 2.0.8
+
+Tue, 28 Jun 2022 09:50:50 GMT
+
+### Patches
+
+- Bump @griffel/babel-preset to v1.3.3
+- Bump @griffel/react to v1.2.1
+
+## 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/README.md b/packages/webpack-loader/README.md
index a2e54b2..1331fa8 100644
--- a/packages/webpack-loader/README.md
+++ b/packages/webpack-loader/README.md
@@ -7,7 +7,7 @@ A loader for Webpack 5 that performs build time transforms for [`@griffel/react`
 ```bash
 yarn add --dev @griffel/webpack-loader
 # or
-npm install --dev @griffel/webpack-loader
+npm install --save-dev @griffel/webpack-loader
 ```
 
 ## When to use it?
diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json
index 328fd03..afcd12b 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.8",
   "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.3",
     "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.1",
     "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/workspace.json b/workspace.json
index 0696a83..59a619b 100644
--- a/workspace.json
+++ b/workspace.json
@@ -1,6 +1,7 @@
 {
   "version": 2,
   "projects": {
+    "@griffel/benchmark": "apps/benchmark",
     "@griffel/babel-preset": "packages/babel-preset",
     "@griffel/core": "packages/core",
     "@griffel/devtools": "packages/devtools",
@@ -9,6 +10,7 @@
     "@griffel/jest-serializer": "packages/jest-serializer",
     "@griffel/react": "packages/react",
     "@griffel/webpack-loader": "packages/webpack-loader",
+    "@griffel/webpack-extraction-plugin": "packages/webpack-extraction-plugin",
     "@griffel/website": "apps/website"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index 46a253d..18261ce 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -181,19 +181,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3":
-  version: 7.16.7
-  resolution: "@babel/code-frame@npm:7.16.7"
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3":
+  version: 7.18.6
+  resolution: "@babel/code-frame@npm:7.18.6"
   dependencies:
-    "@babel/highlight": ^7.16.7
-  checksum: db2f7faa31bc2c9cf63197b481b30ea57147a5fc1a6fab60e5d6c02cdfbf6de8e17b5121f99917b3dabb5eeb572da078312e70697415940383efc140d4e0808b
+    "@babel/highlight": ^7.18.6
+  checksum: 195e2be3172d7684bf95cff69ae3b7a15a9841ea9d27d3c843662d50cdd7d6470fd9c8e64be84d031117e4a4083486effba39f9aef6bbb2c89f7f21bcfba33ba
   languageName: node
   linkType: hard
 
-"@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.16.4, @babel/compat-data@npm:^7.16.8":
-  version: 7.16.8
-  resolution: "@babel/compat-data@npm:7.16.8"
-  checksum: 10da2dac5ea9589c251412b00920889910e476c1ab24cd7095577635bc3a27c785151c89db4e26285fd39f509510ec29ab9d7e721f4fc16e4aec221cacde784b
+"@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.16.4, @babel/compat-data@npm:^7.16.8, @babel/compat-data@npm:^7.18.6":
+  version: 7.18.8
+  resolution: "@babel/compat-data@npm:7.18.8"
+  checksum: 3096aafad74936477ebdd039bcf342fba84eb3100e608f3360850fb63e1efa1c66037c4824f814d62f439ab47d25164439343a6e92e9b4357024fdf571505eb9
   languageName: node
   linkType: hard
 
@@ -221,37 +221,37 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/core@npm:^7.1.0, @babel/core@npm:^7.10.4, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.13, @babel/core@npm:^7.12.3, @babel/core@npm:^7.15.0, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.5, @babel/core@npm:^7.8.0":
-  version: 7.17.5
-  resolution: "@babel/core@npm:7.17.5"
+"@babel/core@npm:^7.1.0, @babel/core@npm:^7.10.4, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.13, @babel/core@npm:^7.12.3, @babel/core@npm:^7.15.0, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.18.2, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.5, @babel/core@npm:^7.8.0":
+  version: 7.18.6
+  resolution: "@babel/core@npm:7.18.6"
   dependencies:
     "@ampproject/remapping": ^2.1.0
-    "@babel/code-frame": ^7.16.7
-    "@babel/generator": ^7.17.3
-    "@babel/helper-compilation-targets": ^7.16.7
-    "@babel/helper-module-transforms": ^7.16.7
-    "@babel/helpers": ^7.17.2
-    "@babel/parser": ^7.17.3
-    "@babel/template": ^7.16.7
-    "@babel/traverse": ^7.17.3
-    "@babel/types": ^7.17.0
+    "@babel/code-frame": ^7.18.6
+    "@babel/generator": ^7.18.6
+    "@babel/helper-compilation-targets": ^7.18.6
+    "@babel/helper-module-transforms": ^7.18.6
+    "@babel/helpers": ^7.18.6
+    "@babel/parser": ^7.18.6
+    "@babel/template": ^7.18.6
+    "@babel/traverse": ^7.18.6
+    "@babel/types": ^7.18.6
     convert-source-map: ^1.7.0
     debug: ^4.1.0
     gensync: ^1.0.0-beta.2
-    json5: ^2.1.2
+    json5: ^2.2.1
     semver: ^6.3.0
-  checksum: c5e7dddb4feaacb91175d22a6edc8e93804242328a82b80732c6e84a0647bc0a9c9d5b05f3ce13138b8e59bf7aba4ff9f7b7446302f141f243ba51df02c318a5
+  checksum: 711459ebf7afab7b8eff88b7155c3f4a62690545f1c8c2eb6ba5ebaed01abeecb984cf9657847a2151ad24a5645efce765832aa343ce0f0386f311b67b59589a
   languageName: node
   linkType: hard
 
-"@babel/generator@npm:>=7, @babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.13, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.16.0, @babel/generator@npm:^7.17.3, @babel/generator@npm:^7.7.2":
-  version: 7.17.3
-  resolution: "@babel/generator@npm:7.17.3"
+"@babel/generator@npm:>=7, @babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.13, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.16.0, @babel/generator@npm:^7.18.6, @babel/generator@npm:^7.18.7, @babel/generator@npm:^7.7.2":
+  version: 7.18.7
+  resolution: "@babel/generator@npm:7.18.7"
   dependencies:
-    "@babel/types": ^7.17.0
+    "@babel/types": ^7.18.7
+    "@jridgewell/gen-mapping": ^0.3.2
     jsesc: ^2.5.1
-    source-map: ^0.5.0
-  checksum: ddf70e3489976018dfc2da8b9f43ec8c582cac2da681ed4a6227c53b26a9626223e4dca90098b3d3afe43bc67f20160856240e826c56b48e577f34a5a7e22b9f
+  checksum: aad4b6873130165e9483af2888bce5a3a5ad9cca0757fc90ae11a0396757d0b295a3bff49282c8df8ab01b31972cc855ae88fd9ddc9ab00d9427dc0e01caeea9
   languageName: node
   linkType: hard
 
@@ -274,17 +274,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-compilation-targets@npm:7.16.7"
+"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.16.7, @babel/helper-compilation-targets@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-compilation-targets@npm:7.18.6"
   dependencies:
-    "@babel/compat-data": ^7.16.4
-    "@babel/helper-validator-option": ^7.16.7
-    browserslist: ^4.17.5
+    "@babel/compat-data": ^7.18.6
+    "@babel/helper-validator-option": ^7.18.6
+    browserslist: ^4.20.2
     semver: ^6.3.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 7238aaee78c011a42fb5ca92e5eff098752f7b314c2111d7bb9cdd58792fcab1b9c819b59f6a0851dc210dc09dc06b30d130a23982753e70eb3111bc65204842
+  checksum: f09ddaddc83c241cb7a040025e2ba558daa1c950ce878604d91230aed8d8a90f10dfd5bb0b67bc5b3db8af1576a0d0dac1d65959a06a17259243dbb5730d0ed1
   languageName: node
   linkType: hard
 
@@ -353,12 +353,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-environment-visitor@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-environment-visitor@npm:7.16.7"
-  dependencies:
-    "@babel/types": ^7.16.7
-  checksum: c03a10105d9ebd1fe632a77356b2e6e2f3c44edba9a93b0dc3591b6a66bd7a2e323dd9502f9ce96fc6401234abff1907aa877b6674f7826b61c953f7c8204bbe
+"@babel/helper-environment-visitor@npm:^7.16.7, @babel/helper-environment-visitor@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-environment-visitor@npm:7.18.6"
+  checksum: 64fce65a26efb50d2496061ab2de669dc4c42175a8e05c82279497127e5c542538ed22b38194f6f5a4e86bed6ef5a4890aed23408480db0555728b4ca660fc9c
   languageName: node
   linkType: hard
 
@@ -371,32 +369,31 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-function-name@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-function-name@npm:7.16.7"
+"@babel/helper-function-name@npm:^7.16.7, @babel/helper-function-name@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-function-name@npm:7.18.6"
   dependencies:
-    "@babel/helper-get-function-arity": ^7.16.7
-    "@babel/template": ^7.16.7
-    "@babel/types": ^7.16.7
-  checksum: fc77cbe7b10cfa2a262d7a37dca575c037f20419dfe0c5d9317f589599ca24beb5f5c1057748011159149eaec47fe32338c6c6412376fcded68200df470161e1
+    "@babel/template": ^7.18.6
+    "@babel/types": ^7.18.6
+  checksum: bf84c2e0699aa07c3559d4262d199d4a9d0320037c2932efe3246866c3e01ce042c9c2131b5db32ba2409a9af01fb468171052819af759babc8ca93bdc6c9aeb
   languageName: node
   linkType: hard
 
-"@babel/helper-get-function-arity@npm:^7.16.7":
+"@babel/helper-hoist-variables@npm:^7.16.7":
   version: 7.16.7
-  resolution: "@babel/helper-get-function-arity@npm:7.16.7"
+  resolution: "@babel/helper-hoist-variables@npm:7.16.7"
   dependencies:
     "@babel/types": ^7.16.7
-  checksum: 25d969fb207ff2ad5f57a90d118f6c42d56a0171022e200aaa919ba7dc95ae7f92ec71cdea6c63ef3629a0dc962ab4c78e09ca2b437185ab44539193f796e0c3
+  checksum: 6ae1641f4a751cd9045346e3f61c3d9ec1312fd779ab6d6fecfe2a96e59a481ad5d7e40d2a840894c13b3fd6114345b157f9e3062fc5f1580f284636e722de60
   languageName: node
   linkType: hard
 
-"@babel/helper-hoist-variables@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-hoist-variables@npm:7.16.7"
+"@babel/helper-hoist-variables@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-hoist-variables@npm:7.18.6"
   dependencies:
-    "@babel/types": ^7.16.7
-  checksum: 6ae1641f4a751cd9045346e3f61c3d9ec1312fd779ab6d6fecfe2a96e59a481ad5d7e40d2a840894c13b3fd6114345b157f9e3062fc5f1580f284636e722de60
+    "@babel/types": ^7.18.6
+  checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f
   languageName: node
   linkType: hard
 
@@ -409,28 +406,28 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-module-imports@npm:7.16.7"
+"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-module-imports@npm:7.18.6"
   dependencies:
-    "@babel/types": ^7.16.7
-  checksum: ddd2c4a600a2e9a4fee192ab92bf35a627c5461dbab4af31b903d9ba4d6b6e59e0ff3499fde4e2e9a0eebe24906f00b636f8b4d9bd72ff24d50e6618215c3212
+    "@babel/types": ^7.18.6
+  checksum: f393f8a3b3304b1b7a288a38c10989de754f01d29caf62ce7c4e5835daf0a27b81f3ac687d9d2780d39685aae7b55267324b512150e7b2be967b0c493b6a1def
   languageName: node
   linkType: hard
 
-"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-module-transforms@npm:7.16.7"
+"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.16.7, @babel/helper-module-transforms@npm:^7.18.6":
+  version: 7.18.8
+  resolution: "@babel/helper-module-transforms@npm:7.18.8"
   dependencies:
-    "@babel/helper-environment-visitor": ^7.16.7
-    "@babel/helper-module-imports": ^7.16.7
-    "@babel/helper-simple-access": ^7.16.7
-    "@babel/helper-split-export-declaration": ^7.16.7
-    "@babel/helper-validator-identifier": ^7.16.7
-    "@babel/template": ^7.16.7
-    "@babel/traverse": ^7.16.7
-    "@babel/types": ^7.16.7
-  checksum: 6e930ce776c979f299cdbeaf80187f4ab086d75287b96ecc1c6896d392fcb561065f0d6219fc06fa79b4ceb4bbdc1a9847da8099aba9b077d0a9e583500fb673
+    "@babel/helper-environment-visitor": ^7.18.6
+    "@babel/helper-module-imports": ^7.18.6
+    "@babel/helper-simple-access": ^7.18.6
+    "@babel/helper-split-export-declaration": ^7.18.6
+    "@babel/helper-validator-identifier": ^7.18.6
+    "@babel/template": ^7.18.6
+    "@babel/traverse": ^7.18.8
+    "@babel/types": ^7.18.8
+  checksum: 6aaf436d14495050987b9e0b30259ca58b02cc2466edd0c5d6883d92867e2cc2a311afe5815d5e10ef2511af1fb200de0e593f797b25a6d9a2bb49722bc16d95
   languageName: node
   linkType: hard
 
@@ -450,10 +447,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
-  version: 7.16.7
-  resolution: "@babel/helper-plugin-utils@npm:7.16.7"
-  checksum: d08dd86554a186c2538547cd537552e4029f704994a9201d41d82015c10ed7f58f9036e8d1527c3760f042409163269d308b0b3706589039c5f1884619c6d4ce
+"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.17.12, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
+  version: 7.18.6
+  resolution: "@babel/helper-plugin-utils@npm:7.18.6"
+  checksum: 3dbfceb6c10fdf6c78a0e57f24e991ff8967b8a0bd45fe0314fb4a8ccf7c8ad4c3778c319a32286e7b1f63d507173df56b4e69fb31b71e1b447a73efa1ca723e
   languageName: node
   linkType: hard
 
@@ -481,12 +478,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-simple-access@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-simple-access@npm:7.16.7"
+"@babel/helper-simple-access@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-simple-access@npm:7.18.6"
   dependencies:
-    "@babel/types": ^7.16.7
-  checksum: 8d22c46c5ec2ead0686c4d5a3d1d12b5190c59be676bfe0d9d89df62b437b51d1a3df2ccfb8a77dded2e585176ebf12986accb6d45a18cff229eef3b10344f4b
+    "@babel/types": ^7.18.6
+  checksum: 37cd36eef199e0517845763c1e6ff6ea5e7876d6d707a6f59c9267c547a50aa0e84260ba9285d49acfaf2cfa0a74a772d92967f32ac1024c961517d40b6c16a5
   languageName: node
   linkType: hard
 
@@ -499,26 +496,26 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-split-export-declaration@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-split-export-declaration@npm:7.16.7"
+"@babel/helper-split-export-declaration@npm:^7.16.7, @babel/helper-split-export-declaration@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-split-export-declaration@npm:7.18.6"
   dependencies:
-    "@babel/types": ^7.16.7
-  checksum: e10aaf135465c55114627951b79115f24bc7af72ecbb58d541d66daf1edaee5dde7cae3ec8c3639afaf74526c03ae3ce723444e3b5b3dc77140c456cd84bcaa1
+    "@babel/types": ^7.18.6
+  checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b
   languageName: node
   linkType: hard
 
-"@babel/helper-validator-identifier@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-validator-identifier@npm:7.16.7"
-  checksum: dbb3db9d184343152520a209b5684f5e0ed416109cde82b428ca9c759c29b10c7450657785a8b5c5256aa74acc6da491c1f0cf6b784939f7931ef82982051b69
+"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-validator-identifier@npm:7.18.6"
+  checksum: e295254d616bbe26e48c196a198476ab4d42a73b90478c9842536cf910ead887f5af6b5c4df544d3052a25ccb3614866fa808dc1e3a5a4291acd444e243c0648
   languageName: node
   linkType: hard
 
-"@babel/helper-validator-option@npm:^7.12.11, @babel/helper-validator-option@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/helper-validator-option@npm:7.16.7"
-  checksum: c5ccc451911883cc9f12125d47be69434f28094475c1b9d2ada7c3452e6ac98a1ee8ddd364ca9e3f9855fcdee96cdeafa32543ebd9d17fee7a1062c202e80570
+"@babel/helper-validator-option@npm:^7.12.11, @babel/helper-validator-option@npm:^7.16.7, @babel/helper-validator-option@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helper-validator-option@npm:7.18.6"
+  checksum: f9cc6eb7cc5d759c5abf006402180f8d5e4251e9198197428a97e05d65eb2f8ae5a0ce73b1dfd2d35af41d0eb780627a64edf98a4e71f064eeeacef8de58f2cf
   languageName: node
   linkType: hard
 
@@ -534,34 +531,34 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.17.2":
-  version: 7.17.2
-  resolution: "@babel/helpers@npm:7.17.2"
+"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/helpers@npm:7.18.6"
   dependencies:
-    "@babel/template": ^7.16.7
-    "@babel/traverse": ^7.17.0
-    "@babel/types": ^7.17.0
-  checksum: 5fa06bbf59636314fb4098bb2e70cf488e0fb6989553438abab90356357b79976102ac129fb16fc8186893c79e0809de1d90e3304426d6fcdb1750da2b6dff9d
+    "@babel/template": ^7.18.6
+    "@babel/traverse": ^7.18.6
+    "@babel/types": ^7.18.6
+  checksum: 5dea4fa53776703ae4190cacd3f81464e6e00cf0b6908ea9b0af2b3d9992153f3746dd8c33d22ec198f77a8eaf13a273d83cd8847f7aef983801e7bfafa856ec
   languageName: node
   linkType: hard
 
-"@babel/highlight@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/highlight@npm:7.16.7"
+"@babel/highlight@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/highlight@npm:7.18.6"
   dependencies:
-    "@babel/helper-validator-identifier": ^7.16.7
+    "@babel/helper-validator-identifier": ^7.18.6
     chalk: ^2.0.0
     js-tokens: ^4.0.0
-  checksum: f7e04e7e03b83c2cca984f4d3e180c9b018784f45d03367e94daf983861229ddc47264045f3b58dfeb0007f9c67bc2a76c4de1693bad90e5394876ef55ece5bb
+  checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.4, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.17.3":
-  version: 7.17.3
-  resolution: "@babel/parser@npm:7.17.3"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.4, @babel/parser@npm:^7.18.6, @babel/parser@npm:^7.18.8":
+  version: 7.18.8
+  resolution: "@babel/parser@npm:7.18.8"
   bin:
     parser: ./bin/babel-parser.js
-  checksum: 311869baef97c7630ac3b3c4600da18229b95aa2785b2daab2044384745fe0653070916ade28749fb003f7369a081111ada53e37284ba48d6b5858cbb9e411d1
+  checksum: b8426083f753a000bdb4929cb18c6ce5b68c23759245bf123515bf86cacb9f6e7ff61341a6e0d01a779a9a8a826c86062a0f4db424b88b5b51f67e121985d400
   languageName: node
   linkType: hard
 
@@ -655,26 +652,26 @@ __metadata:
   linkType: hard
 
 "@babel/plugin-proposal-export-default-from@npm:^7.12.1":
-  version: 7.16.7
-  resolution: "@babel/plugin-proposal-export-default-from@npm:7.16.7"
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-export-default-from@npm:7.18.6"
   dependencies:
-    "@babel/helper-plugin-utils": ^7.16.7
-    "@babel/plugin-syntax-export-default-from": ^7.16.7
+    "@babel/helper-plugin-utils": ^7.18.6
+    "@babel/plugin-syntax-export-default-from": ^7.18.6
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: de6d2e4e8c77073ecbfe3cba8fb4db046a80d22a76817ad8e65c1861e3443956b82d931936388059dee2bb4b6c745f9cd16fa390d51a18ea7b56b2e8afdcc6d9
+  checksum: fc5c9d6c05d4c9970c7b1915698b456f23bcd492f0ba16a297429c4fb1ef03dea2ba6eb489805d8e882c4cdb3195ae18c29cfce4a9e99400e6e02b5f3d552d21
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-export-namespace-from@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.16.7"
+"@babel/plugin-proposal-export-namespace-from@npm:>=7, @babel/plugin-proposal-export-namespace-from@npm:^7.16.7":
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.6"
   dependencies:
-    "@babel/helper-plugin-utils": ^7.16.7
+    "@babel/helper-plugin-utils": ^7.18.6
     "@babel/plugin-syntax-export-namespace-from": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 5016079a5305c1c130fea587b42cdce501574739cfefa5b63469dbc1f32d436df0ff42fabf04089fe8b6a00f4ea7563869e944744b457e186c677995983cb166
+  checksum: 3227307e1155e8434825c02fb2e4e91e590aeb629ce6ce23e4fe869d0018a144c4674bf98863e1bb6d4e4a6f831e686ae43f46a87894e4286e31e6492a5571eb
   languageName: node
   linkType: hard
 
@@ -883,14 +880,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-export-default-from@npm:^7.16.7":
-  version: 7.16.7
-  resolution: "@babel/plugin-syntax-export-default-from@npm:7.16.7"
+"@babel/plugin-syntax-export-default-from@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/plugin-syntax-export-default-from@npm:7.18.6"
   dependencies:
-    "@babel/helper-plugin-utils": ^7.16.7
+    "@babel/helper-plugin-utils": ^7.18.6
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 9a2cfcb262ca59e17914cc3b48f3633b82a30bbc18d395a762f04270859d974ccbd3ae9c342484969cacbb10b8d0fb636b445d8a91ec0aae9fa73319d6b5f5c1
+  checksum: 4258156553d825abb2ebac920eae6837087b485eb8e0011e05ad1e57004a03441335325feb18185ffbfa0c33a340673e7ab79549080ff2beb4607f88936fedf2
   languageName: node
   linkType: hard
 
@@ -949,6 +946,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@babel/plugin-syntax-jsx@npm:^7.12.13":
+  version: 7.17.12
+  resolution: "@babel/plugin-syntax-jsx@npm:7.17.12"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.17.12
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 6acd0bbca8c3e0100ad61f3b7d0b0111cd241a0710b120b298c4aa0e07be02eccbcca61ede1e7678ade1783a0979f20305b62263df6767fa3fbf658670d82af5
+  languageName: node
+  linkType: hard
+
 "@babel/plugin-syntax-jsx@npm:^7.16.7":
   version: 7.16.7
   resolution: "@babel/plugin-syntax-jsx@npm:7.16.7"
@@ -1251,17 +1259,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-commonjs@npm:^7.16.8":
-  version: 7.16.8
-  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.16.8"
+"@babel/plugin-transform-modules-commonjs@npm:^7.16.8, @babel/plugin-transform-modules-commonjs@npm:^7.18.2":
+  version: 7.18.6
+  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.18.6"
   dependencies:
-    "@babel/helper-module-transforms": ^7.16.7
-    "@babel/helper-plugin-utils": ^7.16.7
-    "@babel/helper-simple-access": ^7.16.7
+    "@babel/helper-module-transforms": ^7.18.6
+    "@babel/helper-plugin-utils": ^7.18.6
+    "@babel/helper-simple-access": ^7.18.6
     babel-plugin-dynamic-import-node: ^2.3.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: c0ac00f5457e12cac7825b14725b6fc787bef78945181469ff79f07ef0fd7df021cb00fe1d3a9f35fc9bc92ae59e6e3fc9075a70b627dfe10e00d0907892aace
+  checksum: 7e356e3df8a6a8542cced7491ec5b1cc1093a88d216a59e63a5d2b9fe9d193cbea864f680a41429e41a4f9ecec930aa5b0b8f57e2b17b3b4d27923bb12ba5d14
   languageName: node
   linkType: hard
 
@@ -1726,42 +1734,51 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/template@npm:>=7, @babel/template@npm:^7.12.13, @babel/template@npm:^7.12.7, @babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3":
-  version: 7.16.7
-  resolution: "@babel/template@npm:7.16.7"
+"@babel/runtime@npm:^7.13.10":
+  version: 7.18.3
+  resolution: "@babel/runtime@npm:7.18.3"
   dependencies:
-    "@babel/code-frame": ^7.16.7
-    "@babel/parser": ^7.16.7
-    "@babel/types": ^7.16.7
-  checksum: 10cd112e89276e00f8b11b55a51c8b2f1262c318283a980f4d6cdb0286dc05734b9aaeeb9f3ad3311900b09bc913e02343fcaa9d4a4f413964aaab04eb84ac4a
+    regenerator-runtime: ^0.13.4
+  checksum: db8526226aa02cfa35a5a7ac1a34b5f303c62a1f000c7db48cb06c6290e616483e5036ab3c4e7a84d0f3be6d4e2148d5fe5cec9564bf955f505c3e764b83d7f1
   languageName: node
   linkType: hard
 
-"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.13, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.3, @babel/traverse@npm:^7.16.7, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.17.0, @babel/traverse@npm:^7.17.3, @babel/traverse@npm:^7.7.2":
-  version: 7.17.3
-  resolution: "@babel/traverse@npm:7.17.3"
+"@babel/template@npm:>=7, @babel/template@npm:^7.12.13, @babel/template@npm:^7.12.7, @babel/template@npm:^7.16.7, @babel/template@npm:^7.18.6, @babel/template@npm:^7.3.3":
+  version: 7.18.6
+  resolution: "@babel/template@npm:7.18.6"
   dependencies:
-    "@babel/code-frame": ^7.16.7
-    "@babel/generator": ^7.17.3
-    "@babel/helper-environment-visitor": ^7.16.7
-    "@babel/helper-function-name": ^7.16.7
-    "@babel/helper-hoist-variables": ^7.16.7
-    "@babel/helper-split-export-declaration": ^7.16.7
-    "@babel/parser": ^7.17.3
-    "@babel/types": ^7.17.0
+    "@babel/code-frame": ^7.18.6
+    "@babel/parser": ^7.18.6
+    "@babel/types": ^7.18.6
+  checksum: cb02ed804b7b1938dbecef4e01562013b80681843dd391933315b3dd9880820def3b5b1bff6320d6e4c6a1d63d1d5799630d658ec6b0369c5505e7e4029c38fb
+  languageName: node
+  linkType: hard
+
+"@babel/traverse@npm:>=7, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.13, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.3, @babel/traverse@npm:^7.16.7, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.18.8, @babel/traverse@npm:^7.7.2":
+  version: 7.18.8
+  resolution: "@babel/traverse@npm:7.18.8"
+  dependencies:
+    "@babel/code-frame": ^7.18.6
+    "@babel/generator": ^7.18.7
+    "@babel/helper-environment-visitor": ^7.18.6
+    "@babel/helper-function-name": ^7.18.6
+    "@babel/helper-hoist-variables": ^7.18.6
+    "@babel/helper-split-export-declaration": ^7.18.6
+    "@babel/parser": ^7.18.8
+    "@babel/types": ^7.18.8
     debug: ^4.1.0
     globals: ^11.1.0
-  checksum: 780d7ecf711758174989794891af08d378f81febdb8932056c0d9979524bf0298e28f8e7708a872d7781151506c28f56c85c63ea3f1f654662c2fcb8a3eb9fdc
+  checksum: c406e01f45f13a666083f6e4ea32d2d5e20ce3a51ea48f6c8fe9d6a0469069f857af06866749959c4396f191393e39e7e6e7b2a8769afca7f50ca1046d6172bd
   languageName: node
   linkType: hard
 
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.12.7, @babel/types@npm:^7.15.6, @babel/types@npm:^7.16.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.17.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
-  version: 7.17.0
-  resolution: "@babel/types@npm:7.17.0"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.12.7, @babel/types@npm:^7.15.6, @babel/types@npm:^7.16.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.18.8, @babel/types@npm:^7.2.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
+  version: 7.18.8
+  resolution: "@babel/types@npm:7.18.8"
   dependencies:
-    "@babel/helper-validator-identifier": ^7.16.7
+    "@babel/helper-validator-identifier": ^7.18.6
     to-fast-properties: ^2.0.0
-  checksum: 12e5a287986fe557188e87b2c5202223f1dc83d9239a196ab936fdb9f8c1eb0be717ff19f934b5fad4e29a75586d5798f74bed209bccea1c20376b9952056f0e
+  checksum: a485531faa9ff3b83ea94ba6502321dd66e39202c46d7765e4336cb4aff2ff69ebc77d97b17e21331a8eedde1f5490ce00e8a430c1041fc26854d636e6701919
   languageName: node
   linkType: hard
 
@@ -2601,6 +2618,28 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/babel-plugin@npm:^11.7.1":
+  version: 11.9.2
+  resolution: "@emotion/babel-plugin@npm:11.9.2"
+  dependencies:
+    "@babel/helper-module-imports": ^7.12.13
+    "@babel/plugin-syntax-jsx": ^7.12.13
+    "@babel/runtime": ^7.13.10
+    "@emotion/hash": ^0.8.0
+    "@emotion/memoize": ^0.7.5
+    "@emotion/serialize": ^1.0.2
+    babel-plugin-macros: ^2.6.1
+    convert-source-map: ^1.5.0
+    escape-string-regexp: ^4.0.0
+    find-root: ^1.1.0
+    source-map: ^0.5.7
+    stylis: 4.0.13
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: 2d2c4fadd389862896bcbc5f42c9b9c1a199810173fcf14e5520506c7179c2ddb991b8832fd273f42104cf0dae98886ad8e767b5e38ad235b652d903c3b8a328
+  languageName: node
+  linkType: hard
+
 "@emotion/cache@npm:^10.0.27":
   version: 10.0.29
   resolution: "@emotion/cache@npm:10.0.29"
@@ -2613,6 +2652,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/cache@npm:^11.7.1":
+  version: 11.9.3
+  resolution: "@emotion/cache@npm:11.9.3"
+  dependencies:
+    "@emotion/memoize": ^0.7.4
+    "@emotion/sheet": ^1.1.1
+    "@emotion/utils": ^1.0.0
+    "@emotion/weak-memoize": ^0.2.5
+    stylis: 4.0.13
+  checksum: 6e0aab2fa5b9b6b0b9bf66b5328ed44265c23ced16b46c13d2602c3497fabd95299f6cf2c87cbc02b630452aa3cff599c194c538125d744aa135824129698ccc
+  languageName: node
+  linkType: hard
+
 "@emotion/core@npm:^10.1.1":
   version: 10.3.1
   resolution: "@emotion/core@npm:10.3.1"
@@ -2640,6 +2692,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/css@npm:^11.9.0":
+  version: 11.9.0
+  resolution: "@emotion/css@npm:11.9.0"
+  dependencies:
+    "@emotion/babel-plugin": ^11.7.1
+    "@emotion/cache": ^11.7.1
+    "@emotion/serialize": ^1.0.3
+    "@emotion/sheet": ^1.0.3
+    "@emotion/utils": ^1.0.0
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  peerDependenciesMeta:
+    "@babel/core":
+      optional: true
+  checksum: bd83d9af5cf87b82992dea030b5c7db9cc26f8fee508fc8c20257bc040d35b0cc595143c5c8e3f5d6719351f3ae1c53b152899c22b60a2a5ff37d3439304401e
+  languageName: node
+  linkType: hard
+
 "@emotion/hash@npm:0.8.0, @emotion/hash@npm:^0.8.0":
   version: 0.8.0
   resolution: "@emotion/hash@npm:0.8.0"
@@ -2663,6 +2733,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/memoize@npm:^0.7.4, @emotion/memoize@npm:^0.7.5":
+  version: 0.7.5
+  resolution: "@emotion/memoize@npm:0.7.5"
+  checksum: 83da8d4a7649a92c72f960817692bc6be13cc13e107b9f7e878d63766525ed4402881bfeb3cda61145c050281e7e260f114a0a2870515527346f2ef896b915b3
+  languageName: node
+  linkType: hard
+
 "@emotion/serialize@npm:^0.11.15, @emotion/serialize@npm:^0.11.16":
   version: 0.11.16
   resolution: "@emotion/serialize@npm:0.11.16"
@@ -2676,6 +2753,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/serialize@npm:^1.0.2, @emotion/serialize@npm:^1.0.3":
+  version: 1.0.4
+  resolution: "@emotion/serialize@npm:1.0.4"
+  dependencies:
+    "@emotion/hash": ^0.8.0
+    "@emotion/memoize": ^0.7.4
+    "@emotion/unitless": ^0.7.5
+    "@emotion/utils": ^1.0.0
+    csstype: ^3.0.2
+  checksum: e8cc342056734e176ea837fe44035126dea174962db40852a7ced499d258c0056b0fd3c298743c446f9ba0f2647cb42dfb623b8e5783c265deb9eb20138d68e7
+  languageName: node
+  linkType: hard
+
 "@emotion/sheet@npm:0.9.4":
   version: 0.9.4
   resolution: "@emotion/sheet@npm:0.9.4"
@@ -2683,6 +2773,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@emotion/sheet@npm:^1.0.3, @emotion/sheet@npm:^1.1.1":
+  version: 1.1.1
+  resolution: "@emotion/sheet@npm:1.1.1"
+  checksum: b916ac665735ef6dfda26b09f2d3493789d432d649733db9da18c4db0115e7fdadeb8d45f6490320248916bb13d978bba74c914b711ac96f659b76a5e52d5cd2
+  languageName: node
+  linkType: hard
+
 "@emotion/styled-base@npm:^10.3.0":
   version: 10.3.0
   resolution: "@emotion/styled-base@npm:10.3.0"
@@ -2718,7 +2815,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@emotion/unitless@npm:0.7.5":
+"@emotion/unitless@npm:0.7.5, @emotion/unitless@npm:^0.7.5":
   version: 0.7.5
   resolution: "@emotion/unitless@npm:0.7.5"
   checksum: f976e5345b53fae9414a7b2e7a949aa6b52f8bdbcc84458b1ddc0729e77ba1d1dfdff9960e0da60183877873d3a631fa24d9695dd714ed94bcd3ba5196586a6b
@@ -2732,7 +2829,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@emotion/weak-memoize@npm:0.2.5":
+"@emotion/utils@npm:^1.0.0":
+  version: 1.1.0
+  resolution: "@emotion/utils@npm:1.1.0"
+  checksum: d3b681ca3a23b07033ac6c6937e71010a5549ac8ccec325eb6c91a7e48d9a73db83fa5dadc58be981bb125d7c00fedca868ea4362b1da9e02866615f96be4df1
+  languageName: node
+  linkType: hard
+
+"@emotion/weak-memoize@npm:0.2.5, @emotion/weak-memoize@npm:^0.2.5":
   version: 0.2.5
   resolution: "@emotion/weak-memoize@npm:0.2.5"
   checksum: 27d402b0c683b94658220b6d47840346ee582329ca2a15ec9c233492e0f1a27687ccb233b76eedc922f2e185e444cc89f7b97a81a1d3e5ae9f075bab08e965ea
@@ -3103,6 +3207,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jridgewell/gen-mapping@npm:^0.3.2":
+  version: 0.3.2
+  resolution: "@jridgewell/gen-mapping@npm:0.3.2"
+  dependencies:
+    "@jridgewell/set-array": ^1.0.1
+    "@jridgewell/sourcemap-codec": ^1.4.10
+    "@jridgewell/trace-mapping": ^0.3.9
+  checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882
+  languageName: node
+  linkType: hard
+
 "@jridgewell/resolve-uri@npm:^3.0.3":
   version: 3.0.4
   resolution: "@jridgewell/resolve-uri@npm:3.0.4"
@@ -3110,6 +3225,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jridgewell/set-array@npm:^1.0.1":
+  version: 1.1.2
+  resolution: "@jridgewell/set-array@npm:1.1.2"
+  checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e
+  languageName: node
+  linkType: hard
+
 "@jridgewell/sourcemap-codec@npm:^1.4.10":
   version: 1.4.11
   resolution: "@jridgewell/sourcemap-codec@npm:1.4.11"
@@ -3117,13 +3239,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@jridgewell/trace-mapping@npm:^0.3.0":
-  version: 0.3.4
-  resolution: "@jridgewell/trace-mapping@npm:0.3.4"
+"@jridgewell/trace-mapping@npm:^0.3.0, @jridgewell/trace-mapping@npm:^0.3.9":
+  version: 0.3.14
+  resolution: "@jridgewell/trace-mapping@npm:0.3.14"
   dependencies:
     "@jridgewell/resolve-uri": ^3.0.3
     "@jridgewell/sourcemap-codec": ^1.4.10
-  checksum: ab8bce84bbbc8c34f3ba8325ed926f8f2d3098983c10442a80c55764c4eb6e47d5b92d8ff20a0dd868c3e76a3535651fd8a0138182c290dbfc8396195685c37b
+  checksum: b9537b9630ffb631aef9651a085fe361881cde1772cd482c257fe3c78c8fd5388d681f504a9c9fe1081b1c05e8f75edf55ee10fdb58d92bbaa8dbf6a7bd6b18c
   languageName: node
   linkType: hard
 
@@ -3170,78 +3292,79 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@linaria/babel-preset@npm:^3.0.0-beta.14, @linaria/babel-preset@npm:^3.0.0-beta.17":
-  version: 3.0.0-beta.17
-  resolution: "@linaria/babel-preset@npm:3.0.0-beta.17"
+"@linaria/babel-preset@npm:^3.0.0-beta.22":
+  version: 3.0.0-beta.22
+  resolution: "@linaria/babel-preset@npm:3.0.0-beta.22"
   dependencies:
+    "@babel/core": ^7.18.2
     "@babel/generator": ">=7"
+    "@babel/plugin-proposal-export-namespace-from": ">=7"
     "@babel/plugin-syntax-dynamic-import": ">=7"
+    "@babel/plugin-transform-modules-commonjs": ^7.18.2
     "@babel/template": ">=7"
-    "@linaria/core": ^3.0.0-beta.15
-    "@linaria/logger": ^3.0.0-beta.15
-    "@linaria/utils": ^3.0.0-beta.15
+    "@babel/traverse": ">=7"
+    "@linaria/core": ^3.0.0-beta.22
+    "@linaria/logger": ^3.0.0-beta.20
+    "@linaria/utils": ^3.0.0-beta.20
     cosmiconfig: ^5.1.0
+    find-up: ^5.0.0
     source-map: ^0.7.3
     stylis: ^3.5.4
-  peerDependencies:
-    "@babel/core": ">=7"
-  checksum: 7b581f1fda66ca441ecf13f45451c327791a2245d6a02bedd1f4c9fccf6dcd9a8ea12b3a2da0a419bdf551450768a06f052d55e76e5ce5ea3cd6cfd231316286
+  checksum: fb1d1f5366fdb91bb6ca4fcda94e6aabe9602f06782d36c5805d0de67ff26b18adc352d6e2e474da81892bad1ba2669ffe98a13c784f6b48dedd077024be2557
   languageName: node
   linkType: hard
 
-"@linaria/core@npm:^3.0.0-beta.15":
-  version: 3.0.0-beta.15
-  resolution: "@linaria/core@npm:3.0.0-beta.15"
+"@linaria/core@npm:^3.0.0-beta.22":
+  version: 3.0.0-beta.22
+  resolution: "@linaria/core@npm:3.0.0-beta.22"
   dependencies:
-    "@linaria/utils": ^3.0.0-beta.15
-  checksum: 77b3763d10e73712a8126263b93fc063fb7075368aabd059bf37f04e6524a19213aeb5f66dabd509ad138decd0ae1ffe65ae0bf4e1a7c44422be43fd438765f8
+    "@linaria/logger": ^3.0.0-beta.20
+    "@linaria/utils": ^3.0.0-beta.20
+  checksum: 67f57811c78821eb4866e3f307078e3c1cf375ca84f23b4a25cf7f4c34b48b76eaef70a4320436a51145a73b4d0fbac54db7459ab5a23049e7c347fee3644abb
   languageName: node
   linkType: hard
 
-"@linaria/logger@npm:^3.0.0-beta.15":
-  version: 3.0.0-beta.15
-  resolution: "@linaria/logger@npm:3.0.0-beta.15"
+"@linaria/logger@npm:^3.0.0-beta.20":
+  version: 3.0.0-beta.20
+  resolution: "@linaria/logger@npm:3.0.0-beta.20"
   dependencies:
     debug: ^4.1.1
     picocolors: ^1.0.0
-  checksum: fd855f0bfe97f5629e00241f961862f192e64e7d2a7e6dce5d3ef631396d9434def88c699550a2d46a8c45278acea1a0059937cf8f265a98c95c1c8254ccd788
+  checksum: c5c1fbe593d9110fcbf7309517530fbda419c42c4258e0854f05619f917641724718919693a0a12eb50250bace9857435f29f4de8b056e1dc5f40c01eb074a7a
   languageName: node
   linkType: hard
 
-"@linaria/preeval@npm:^3.0.0-beta.17":
-  version: 3.0.0-beta.17
-  resolution: "@linaria/preeval@npm:3.0.0-beta.17"
+"@linaria/preeval@npm:^3.0.0-beta.22":
+  version: 3.0.0-beta.22
+  resolution: "@linaria/preeval@npm:3.0.0-beta.22"
   dependencies:
-    "@linaria/babel-preset": ^3.0.0-beta.17
-  peerDependencies:
-    "@babel/core": ">=7"
-  checksum: 0cfcdb713af3cfc3012b29ca00523a3cf691a559b5deed730293fe84d23a24c0d442b317054ddb98573a68dc16d525efa3b1c5656035241db6080712121b1d83
+    "@linaria/babel-preset": ^3.0.0-beta.22
+  checksum: 3f1cf0c27cbcfd3ebd6e82346ee3ad00f146a8dd2e41b440a677707893e03a82937be5aee3f33fabba2c537b0639d1280cafa139558123548f1473eee08bb446
   languageName: node
   linkType: hard
 
-"@linaria/shaker@npm:^3.0.0-beta.14":
-  version: 3.0.0-beta.17
-  resolution: "@linaria/shaker@npm:3.0.0-beta.17"
+"@linaria/shaker@npm:^3.0.0-beta.22":
+  version: 3.0.0-beta.22
+  resolution: "@linaria/shaker@npm:3.0.0-beta.22"
   dependencies:
+    "@babel/core": ^7.18.2
     "@babel/generator": ">=7"
     "@babel/plugin-transform-runtime": ">=7"
     "@babel/plugin-transform-template-literals": ">=7"
     "@babel/preset-env": ">=7"
-    "@linaria/babel-preset": ^3.0.0-beta.17
-    "@linaria/logger": ^3.0.0-beta.15
-    "@linaria/preeval": ^3.0.0-beta.17
+    "@linaria/babel-preset": ^3.0.0-beta.22
+    "@linaria/logger": ^3.0.0-beta.20
+    "@linaria/preeval": ^3.0.0-beta.22
     babel-plugin-transform-react-remove-prop-types: ^0.4.24
-    ts-invariant: ^0.9.0
-  peerDependencies:
-    "@babel/core": ">=7"
-  checksum: a93cff47b266fca304ab4cfd76e6bd246a720c7eeff501b569344ef5d085a690c3903d4e1822d2ac80dcc06ecba95d3935dd5565d250306efca50a3d09a6a4a1
+    ts-invariant: ^0.10.3
+  checksum: 4b9d3d5112f82317fddbe9369e3b96f5023a4c212aa220e51c32165fe33ba9487683a61263cacb6ad2e202c13e9f1bdba80f0264d3e0074a97a2bcb15af22e9b
   languageName: node
   linkType: hard
 
-"@linaria/utils@npm:^3.0.0-beta.15":
-  version: 3.0.0-beta.15
-  resolution: "@linaria/utils@npm:3.0.0-beta.15"
-  checksum: 05f16972d91a396d3e356a310a037fe1ec4cd7b1ed63923cbb46e583dece687e2342ba77983aa392c4631d841d6df303c6d2883c5c2c484999c58ae4d2bc27a0
+"@linaria/utils@npm:^3.0.0-beta.20":
+  version: 3.0.0-beta.20
+  resolution: "@linaria/utils@npm:3.0.0-beta.20"
+  checksum: dbf2170bf89a06fd7158b979069ca0a445a56baeebe258109a2490d4709dacc5c4b1a3628cd179ba991478809907847e0460c0c32ac92f3d427f9056e2b8c152
   languageName: node
   linkType: hard
 
@@ -5886,6 +6009,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/d3-scale-chromatic@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "@types/d3-scale-chromatic@npm:3.0.0"
+  checksum: e06afffd2725570aa90cb3050eb96a94727264948d9256e56807ab582aba379168d84d1d98bcaa275bf38375148b35dfe13697e06fc7565dd17ac7e2acb11980
+  languageName: node
+  linkType: hard
+
 "@types/eslint-scope@npm:^3.7.0":
   version: 3.7.3
   resolution: "@types/eslint-scope@npm:3.7.3"
@@ -6141,6 +6271,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"
@@ -6575,6 +6714,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/yargs@npm:^17.0.10":
+  version: 17.0.10
+  resolution: "@types/yargs@npm:17.0.10"
+  dependencies:
+    "@types/yargs-parser": "*"
+  checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8
+  languageName: node
+  linkType: hard
+
 "@typescript-eslint/eslint-plugin@npm:~5.3.0":
   version: 5.3.1
   resolution: "@typescript-eslint/eslint-plugin@npm:5.3.1"
@@ -8097,7 +8245,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"babel-plugin-macros@npm:^2.0.0, babel-plugin-macros@npm:^2.8.0":
+"babel-plugin-macros@npm:^2.0.0, babel-plugin-macros@npm:^2.6.1, babel-plugin-macros@npm:^2.8.0":
   version: 2.8.0
   resolution: "babel-plugin-macros@npm:2.8.0"
   dependencies:
@@ -8660,18 +8808,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.16.6, browserslist@npm:^4.17.5, browserslist@npm:^4.18.1, browserslist@npm:^4.19.1":
-  version: 4.19.1
-  resolution: "browserslist@npm:4.19.1"
+"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.19.1, browserslist@npm:^4.20.2":
+  version: 4.21.1
+  resolution: "browserslist@npm:4.21.1"
   dependencies:
-    caniuse-lite: ^1.0.30001286
-    electron-to-chromium: ^1.4.17
-    escalade: ^3.1.1
-    node-releases: ^2.0.1
-    picocolors: ^1.0.0
+    caniuse-lite: ^1.0.30001359
+    electron-to-chromium: ^1.4.172
+    node-releases: ^2.0.5
+    update-browserslist-db: ^1.0.4
   bin:
     browserslist: cli.js
-  checksum: c0777fd483691638fd6801e16c9d809e1d65f6d2b06db2e806654be51045cbab1452a89841a2c5caea2cbe19d621b4f1d391cffbb24512aa33280039ab345875
+  checksum: 4904a9ded0702381adc495e003e7f77970abb7f8c8b8edd9e54f026354b5a96b1bddc26e6d9a7df9f043e468ecd2fcff2c8f40fc489909a042880117c2aca8ff
   languageName: node
   linkType: hard
 
@@ -8982,10 +9129,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001251, caniuse-lite@npm:^1.0.30001286, caniuse-lite@npm:^1.0.30001297":
-  version: 1.0.30001306
-  resolution: "caniuse-lite@npm:1.0.30001306"
-  checksum: 6e80988025b7f770479cea1331f608c9430aa097254c1a0c4a904dbcd46002934489ae00d60b87c14fe14f945d0ee1cd6703c2e1e7725e32a380301ac96a4730
+"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001251, caniuse-lite@npm:^1.0.30001297, caniuse-lite@npm:^1.0.30001359":
+  version: 1.0.30001366
+  resolution: "caniuse-lite@npm:1.0.30001366"
+  checksum: eeb878e0be4090a4247dd3de5392ff1a864d086e5401790c7c81697918ce6ce3dac75956a21f9404b5ac770bfdabdb18619d0f920dc2295f3211ee893355f697
   languageName: node
   linkType: hard
 
@@ -10442,6 +10589,32 @@ __metadata:
   languageName: node
   linkType: hard
 
+"d3-color@npm:1 - 3":
+  version: 3.1.0
+  resolution: "d3-color@npm:3.1.0"
+  checksum: 4931fbfda5d7c4b5cfa283a13c91a954f86e3b69d75ce588d06cde6c3628cebfc3af2069ccf225e982e8987c612aa7948b3932163ce15eb3c11cd7c003f3ee3b
+  languageName: node
+  linkType: hard
+
+"d3-interpolate@npm:1 - 3":
+  version: 3.0.1
+  resolution: "d3-interpolate@npm:3.0.1"
+  dependencies:
+    d3-color: 1 - 3
+  checksum: a42ba314e295e95e5365eff0f604834e67e4a3b3c7102458781c477bd67e9b24b6bb9d8e41ff5521050a3f2c7c0c4bbbb6e187fd586daa3980943095b267e78b
+  languageName: node
+  linkType: hard
+
+"d3-scale-chromatic@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "d3-scale-chromatic@npm:3.0.0"
+  dependencies:
+    d3-color: 1 - 3
+    d3-interpolate: 1 - 3
+  checksum: a8ce4cb0267a17b28ebbb929f5e3071d985908a9c13b6fcaa2a198e1e018f275804d691c5794b970df0049725b7944f32297b31603d235af6414004f0c7f82c0
+  languageName: node
+  linkType: hard
+
 "d@npm:1, d@npm:^1.0.1":
   version: 1.0.1
   resolution: "d@npm:1.0.1"
@@ -11101,10 +11274,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron-to-chromium@npm:^1.4.17":
-  version: 1.4.44
-  resolution: "electron-to-chromium@npm:1.4.44"
-  checksum: 76b0c723cf4fa3f542e4e34add8c559d5c3668192d1f359d24b45546b34791dff716e9fd905fbcddd6875cb849b72106c6f5edd682bba2f78c1c2eb6bccd647d
+"electron-to-chromium@npm:^1.4.172":
+  version: 1.4.187
+  resolution: "electron-to-chromium@npm:1.4.187"
+  checksum: 258be88d92b3f34ffba8ea1d90ecc918f6aa74954af67d0439a649648a17738dad3042ef3f26be141177f5ffb0c5c90dfcaa18ae4dba7bc2937ff21d64a9acb6
   languageName: node
   linkType: hard
 
@@ -11447,6 +11620,233 @@ __metadata:
   languageName: node
   linkType: hard
 
+"esbuild-android-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-android-64@npm:0.14.43"
+  conditions: os=android & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-android-arm64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-android-arm64@npm:0.14.43"
+  conditions: os=android & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"esbuild-darwin-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-darwin-64@npm:0.14.43"
+  conditions: os=darwin & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-darwin-arm64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-darwin-arm64@npm:0.14.43"
+  conditions: os=darwin & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"esbuild-freebsd-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-freebsd-64@npm:0.14.43"
+  conditions: os=freebsd & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-freebsd-arm64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-freebsd-arm64@npm:0.14.43"
+  conditions: os=freebsd & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-32@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-32@npm:0.14.43"
+  conditions: os=linux & cpu=ia32
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-64@npm:0.14.43"
+  conditions: os=linux & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-arm64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-arm64@npm:0.14.43"
+  conditions: os=linux & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-arm@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-arm@npm:0.14.43"
+  conditions: os=linux & cpu=arm
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-mips64le@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-mips64le@npm:0.14.43"
+  conditions: os=linux & cpu=mips64el
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-ppc64le@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-ppc64le@npm:0.14.43"
+  conditions: os=linux & cpu=ppc64
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-riscv64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-riscv64@npm:0.14.43"
+  conditions: os=linux & cpu=riscv64
+  languageName: node
+  linkType: hard
+
+"esbuild-linux-s390x@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-linux-s390x@npm:0.14.43"
+  conditions: os=linux & cpu=s390x
+  languageName: node
+  linkType: hard
+
+"esbuild-netbsd-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-netbsd-64@npm:0.14.43"
+  conditions: os=netbsd & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-openbsd-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-openbsd-64@npm:0.14.43"
+  conditions: os=openbsd & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-plugin-alias@npm:^0.2.1":
+  version: 0.2.1
+  resolution: "esbuild-plugin-alias@npm:0.2.1"
+  checksum: afe2d2c8b5f09d5321cb8d9c0825e8a9f6e03c2d50df92f953a291d4620cc29eddb3da9e33b238f6d8f77738e0277bdcb831f127399449fecf78fb84c04e5da9
+  languageName: node
+  linkType: hard
+
+"esbuild-plugin-import-glob@npm:^0.1.1":
+  version: 0.1.1
+  resolution: "esbuild-plugin-import-glob@npm:0.1.1"
+  dependencies:
+    fast-glob: ^3.2.5
+  checksum: d6a350e13e22bca2183b939040a4f7d3d775ea41fe215f1487aaac17e0477e9bea4afb507c89a17ddaf9c91d8791f567ec98c4a787d3176df9206d67d671a58c
+  languageName: node
+  linkType: hard
+
+"esbuild-sunos-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-sunos-64@npm:0.14.43"
+  conditions: os=sunos & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-windows-32@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-windows-32@npm:0.14.43"
+  conditions: os=win32 & cpu=ia32
+  languageName: node
+  linkType: hard
+
+"esbuild-windows-64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-windows-64@npm:0.14.43"
+  conditions: os=win32 & cpu=x64
+  languageName: node
+  linkType: hard
+
+"esbuild-windows-arm64@npm:0.14.43":
+  version: 0.14.43
+  resolution: "esbuild-windows-arm64@npm:0.14.43"
+  conditions: os=win32 & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"esbuild@npm:^0.14.43":
+  version: 0.14.43
+  resolution: "esbuild@npm:0.14.43"
+  dependencies:
+    esbuild-android-64: 0.14.43
+    esbuild-android-arm64: 0.14.43
+    esbuild-darwin-64: 0.14.43
+    esbuild-darwin-arm64: 0.14.43
+    esbuild-freebsd-64: 0.14.43
+    esbuild-freebsd-arm64: 0.14.43
+    esbuild-linux-32: 0.14.43
+    esbuild-linux-64: 0.14.43
+    esbuild-linux-arm: 0.14.43
+    esbuild-linux-arm64: 0.14.43
+    esbuild-linux-mips64le: 0.14.43
+    esbuild-linux-ppc64le: 0.14.43
+    esbuild-linux-riscv64: 0.14.43
+    esbuild-linux-s390x: 0.14.43
+    esbuild-netbsd-64: 0.14.43
+    esbuild-openbsd-64: 0.14.43
+    esbuild-sunos-64: 0.14.43
+    esbuild-windows-32: 0.14.43
+    esbuild-windows-64: 0.14.43
+    esbuild-windows-arm64: 0.14.43
+  dependenciesMeta:
+    esbuild-android-64:
+      optional: true
+    esbuild-android-arm64:
+      optional: true
+    esbuild-darwin-64:
+      optional: true
+    esbuild-darwin-arm64:
+      optional: true
+    esbuild-freebsd-64:
+      optional: true
+    esbuild-freebsd-arm64:
+      optional: true
+    esbuild-linux-32:
+      optional: true
+    esbuild-linux-64:
+      optional: true
+    esbuild-linux-arm:
+      optional: true
+    esbuild-linux-arm64:
+      optional: true
+    esbuild-linux-mips64le:
+      optional: true
+    esbuild-linux-ppc64le:
+      optional: true
+    esbuild-linux-riscv64:
+      optional: true
+    esbuild-linux-s390x:
+      optional: true
+    esbuild-netbsd-64:
+      optional: true
+    esbuild-openbsd-64:
+      optional: true
+    esbuild-sunos-64:
+      optional: true
+    esbuild-windows-32:
+      optional: true
+    esbuild-windows-64:
+      optional: true
+    esbuild-windows-arm64:
+      optional: true
+  bin:
+    esbuild: bin/esbuild
+  checksum: c5988ba9d3b62c794aaf6752ca4cf0f009d0dc127a19d21c1bbeb2fe6d983c7fe39f3d330e2c00d5a9b6de4a71bfb0f5f347e69c37eb545ab61dc4824b5b2bb3
+  languageName: node
+  linkType: hard
+
 "escalade@npm:^3.1.1":
   version: 3.1.1
   resolution: "escalade@npm:3.1.1"
@@ -12143,7 +12543,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9":
+"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9":
   version: 3.2.11
   resolution: "fast-glob@npm:3.2.11"
   dependencies:
@@ -13263,9 +13663,10 @@ __metadata:
     "@docusaurus/core": 2.0.0-beta.15
     "@docusaurus/module-type-aliases": ^2.0.0-beta.15
     "@docusaurus/preset-classic": 2.0.0-beta.15
+    "@emotion/css": ^11.9.0
     "@emotion/hash": ^0.8.0
-    "@linaria/babel-preset": ^3.0.0-beta.14
-    "@linaria/shaker": ^3.0.0-beta.14
+    "@linaria/babel-preset": ^3.0.0-beta.22
+    "@linaria/shaker": ^3.0.0-beta.22
     "@nrwl/cli": 13.4.5
     "@nrwl/eslint-plugin-nx": 13.4.5
     "@nrwl/jest": 13.4.5
@@ -13288,15 +13689,18 @@ __metadata:
     "@tsconfig/docusaurus": ^1.0.4
     "@types/babel__helper-plugin-utils": 7.10.0
     "@types/chrome": 0.0.180
+    "@types/d3-scale-chromatic": ^3.0.0
     "@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
     "@types/stylis": 4.0.2
     "@types/tmp": 0.2.3
     "@types/webpack-env": ^1.17.0
+    "@types/yargs": ^17.0.10
     "@typescript-eslint/eslint-plugin": ~5.3.0
     "@typescript-eslint/parser": ~5.3.0
     "@typescript-eslint/utils": ^5.20.0
@@ -13308,7 +13712,11 @@ __metadata:
     beachball: ^2.20.0
     bestzip: 2.2.0
     csstype: ^3.0.10
+    d3-scale-chromatic: ^3.0.0
     enhanced-resolve: ^5.8.2
+    esbuild: ^0.14.43
+    esbuild-plugin-alias: ^0.2.1
+    esbuild-plugin-import-glob: ^0.1.1
     eslint: 8.2.0
     eslint-config-prettier: 8.1.0
     eslint-import-resolver-typescript: 2.5.0
@@ -13321,6 +13729,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
@@ -13339,6 +13748,7 @@ __metadata:
     tslib: ^2.1.0
     typescript: ~4.5.2
     url-loader: ^3.0.0
+    yargs: ^17.5.1
   languageName: unknown
   linkType: soft
 
@@ -15913,14 +16323,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.1.3":
-  version: 2.2.0
-  resolution: "json5@npm:2.2.0"
-  dependencies:
-    minimist: ^1.2.5
+"json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.1":
+  version: 2.2.1
+  resolution: "json5@npm:2.2.1"
   bin:
     json5: lib/cli.js
-  checksum: e88fc5274bb58fc99547baa777886b069d2dd96d9cfc4490b305fd16d711dabd5979e35a4f90873cefbeb552e216b041a304fe56702bedba76e19bc7845f208d
+  checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b
   languageName: node
   linkType: hard
 
@@ -16997,6 +17405,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"
@@ -17545,10 +17964,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"node-releases@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "node-releases@npm:2.0.1"
-  checksum: b20dd8d4bced11f75060f0387e05e76b9dc4a0451f7bb3516eade6f50499ea7768ba95d8a60d520c193402df1e58cb3fe301510cc1c1ad68949c3d57b5149866
+"node-releases@npm:^2.0.5":
+  version: 2.0.6
+  resolution: "node-releases@npm:2.0.6"
+  checksum: e86a926dc9fbb3b41b4c4a89d998afdf140e20a4e8dbe6c0a807f7b2948b42ea97d7fd3ad4868041487b6e9ee98409829c6e4d84a734a4215dff060a7fbeb4bf
   languageName: node
   linkType: hard
 
@@ -22287,6 +22706,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"stylis@npm:4.0.13, stylis@npm:^4.0.13":
+  version: 4.0.13
+  resolution: "stylis@npm:4.0.13"
+  checksum: 8ea7a87028b6383c6a982231c4b5b6150031ce028e0fdaf7b2ace82253d28a8af50cc5a9da8a421d3c7c4441592f393086e332795add672aa4a825f0fe3713a3
+  languageName: node
+  linkType: hard
+
 "stylis@npm:^3.5.4":
   version: 3.5.4
   resolution: "stylis@npm:3.5.4"
@@ -22294,13 +22720,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"stylis@npm:^4.0.13":
-  version: 4.0.13
-  resolution: "stylis@npm:4.0.13"
-  checksum: 8ea7a87028b6383c6a982231c4b5b6150031ce028e0fdaf7b2ace82253d28a8af50cc5a9da8a421d3c7c4441592f393086e332795add672aa4a825f0fe3713a3
-  languageName: node
-  linkType: hard
-
 "stylus-loader@npm:^6.2.0":
   version: 6.2.0
   resolution: "stylus-loader@npm:6.2.0"
@@ -22902,12 +23321,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ts-invariant@npm:^0.9.0":
-  version: 0.9.4
-  resolution: "ts-invariant@npm:0.9.4"
+"ts-invariant@npm:^0.10.3":
+  version: 0.10.3
+  resolution: "ts-invariant@npm:0.10.3"
   dependencies:
     tslib: ^2.1.0
-  checksum: c9e5726361fa266916966b2070605f8664b6dd1d8b0ef7565dbf056abb6a87be26195985ef62dd97aeb0894cf2f4ad5b7f0d89dadadc197eaa38e99222afa29c
+  checksum: bb07d56fe4aae69d8860e0301dfdee2d375281159054bc24bf1e49e513fb0835bf7f70a11351344d213a79199c5e695f37ebbf5a447188a377ce0cd81d91ddb5
   languageName: node
   linkType: hard
 
@@ -23478,6 +23897,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"update-browserslist-db@npm:^1.0.4":
+  version: 1.0.4
+  resolution: "update-browserslist-db@npm:1.0.4"
+  dependencies:
+    escalade: ^3.1.1
+    picocolors: ^1.0.0
+  peerDependencies:
+    browserslist: ">= 4.21.0"
+  bin:
+    browserslist-lint: cli.js
+  checksum: 7c7da28d0fc733b17e01c8fa9385ab909eadce64b8ea644e9603867dc368c2e2a6611af8247e72612b23f9e7cb87ac7c7585a05ff94e1759e9d646cbe9bf49a7
+  languageName: node
+  linkType: hard
+
 "update-notifier@npm:^5.1.0":
   version: 5.1.0
   resolution: "update-notifier@npm:5.1.0"
@@ -24609,6 +25042,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"yargs-parser@npm:^21.0.0":
+  version: 21.0.1
+  resolution: "yargs-parser@npm:21.0.1"
+  checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a
+  languageName: node
+  linkType: hard
+
 "yargs@npm:15.4.1":
   version: 15.4.1
   resolution: "yargs@npm:15.4.1"
@@ -24661,6 +25101,21 @@ __metadata:
   languageName: node
   linkType: hard
 
+"yargs@npm:^17.5.1":
+  version: 17.5.1
+  resolution: "yargs@npm:17.5.1"
+  dependencies:
+    cliui: ^7.0.2
+    escalade: ^3.1.1
+    get-caller-file: ^2.0.5
+    require-directory: ^2.1.1
+    string-width: ^4.2.3
+    y18n: ^5.0.5
+    yargs-parser: ^21.0.0
+  checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde
+  languageName: node
+  linkType: hard
+
 "yn@npm:3.1.1":
   version: 3.1.1
   resolution: "yn@npm:3.1.1"


Test patch:
diff --git a/apps/benchmark/src/tests.ts b/apps/benchmark/src/tests.ts
new file mode 100644
index 0000000..ccbefbf
--- /dev/null
+++ b/apps/benchmark/src/tests.ts
@@ -0,0 +1,58 @@
+import implementations from './implementations';
+import Tree from './scenarios/Tree';
+import SierpinskiTriangle from './scenarios/SierpinskiTriangle';
+import { BenchmarkParameters, ComponentsType } from './types';
+
+const packageNames = Object.keys(implementations);
+
+interface BenchmarkTest extends BenchmarkParameters {
+  version: string;
+  name: string;
+}
+
+const createTestBlock = (fn: (components: ComponentsType) => BenchmarkParameters) => {
+  return packageNames.reduce((testSetups, packageName) => {
+    const { name, components, version } = implementations[packageName];
+    // TODO handle Provider
+    const { Component, getComponentProps, sampleCount, type } = fn(components);
+
+    testSetups[packageName] = {
+      Component,
+      getComponentProps,
+      sampleCount,
+      // Provider,
+      type,
+      version,
+      name,
+    };
+    return testSetups;
+  }, {} as Record<string, BenchmarkTest>);
+};
+
+const tests = {
+  'Mount deep tree': createTestBlock(components => ({
+    type: 'mount' as const,
+    Component: Tree,
+    getComponentProps: () => ({ breadth: 2, components, depth: 8, id: 0, wrap: 1 }),
+    Provider: components.Provider,
+    sampleCount: 50,
+  })),
+  'Mount wide tree': createTestBlock(components => ({
+    type: 'mount',
+    Component: Tree,
+    getComponentProps: () => ({ breadth: 6, components, depth: 3, id: 0, wrap: 2 }),
+    Provider: components.Provider,
+    sampleCount: 50,
+  })),
+  'Update dynamic styles': createTestBlock(components => ({
+    type: 'update',
+    Component: SierpinskiTriangle,
+    getComponentProps: ({ cycle }: { cycle: number }) => {
+      return { components, s: 200, renderCount: cycle, x: 0, y: 0 };
+    },
+    Provider: components.Provider,
+    sampleCount: 100,
+  })),
+};
+
+export default tests;
diff --git a/packages/core/src/makeStyles.test.ts b/packages/core/src/makeStyles.test.ts
index 71eed6a..4064348 100644
--- a/packages/core/src/makeStyles.test.ts
+++ b/packages/core/src/makeStyles.test.ts
@@ -188,7 +188,7 @@ describe('makeStyles', () => {
     // Classes emitted by different renderers can be the same
     expect(classesA).toBe(classesB);
     // Style elements should be different for different renderers
-    expect(rendererA.styleElements.d).not.toBe(rendererB.styleElements.d);
+    expect(rendererA.stylesheets.d).not.toBe(rendererB.stylesheets.d);
 
     expect(rendererA).toMatchInlineSnapshot(`
       .f22iagw {
diff --git a/packages/core/src/renderer/createIsomorphicStyleElement-node.test.ts b/packages/core/src/renderer/createIsomorphicStyleElement-node.test.ts
new file mode 100644
index 0000000..d491e5f
--- /dev/null
+++ b/packages/core/src/renderer/createIsomorphicStyleElement-node.test.ts
@@ -0,0 +1,29 @@
+/*
+ * @jest-environment node
+ */
+
+// 👆 this is intentionally to test in SSR like environment
+
+import { createIsomorphicStyleSheet } from './createIsomorphicStyleSheet';
+
+describe('createIsomorphicStyleElement - node', () => {
+  it('should insert css rule', () => {
+    const stylesheet = createIsomorphicStyleSheet(undefined, 'd', {});
+    stylesheet.insertRule(".foo { color: 'red' }");
+    expect(stylesheet.cssRules()).toMatchInlineSnapshot(`
+      Array [
+        ".foo { color: 'red' }",
+      ]
+    `);
+  });
+
+  it('should set element attributes', () => {
+    const stylesheet = createIsomorphicStyleSheet(undefined, 'd', { 'data-foo': 'foo' });
+    expect(stylesheet.elementAttributes).toMatchInlineSnapshot(`
+      Object {
+        "data-foo": "foo",
+        "data-make-styles-bucket": "d",
+      }
+    `);
+  });
+});
diff --git a/packages/core/src/renderer/createIsomorphicStyleSheet.test.ts b/packages/core/src/renderer/createIsomorphicStyleSheet.test.ts
new file mode 100644
index 0000000..818764c
--- /dev/null
+++ b/packages/core/src/renderer/createIsomorphicStyleSheet.test.ts
@@ -0,0 +1,47 @@
+import { createIsomorphicStyleSheet } from './createIsomorphicStyleSheet';
+
+describe('createIsomorphicStyleElement', () => {
+  it('should insert css rule', () => {
+    const stylesheet = createIsomorphicStyleSheet(document.createElement('style'), 'd', {});
+    stylesheet.insertRule(".foo { color: 'red' }");
+    expect(stylesheet.cssRules()).toMatchInlineSnapshot(`
+      Array [
+        ".foo { color: 'red' }",
+      ]
+    `);
+  });
+
+  it('should set element attributes', () => {
+    const stylesheet = createIsomorphicStyleSheet(document.createElement('style'), 'd', { 'data-foo': 'foo' });
+    expect(stylesheet.elementAttributes).toMatchInlineSnapshot(`
+      Object {
+        "data-foo": "foo",
+        "data-make-styles-bucket": "d",
+      }
+    `);
+
+    expect(stylesheet.element).toMatchInlineSnapshot(`
+      <style
+        data-foo="foo"
+        data-make-styles-bucket="d"
+      />
+    `);
+
+    const actualElementAttributes = Array.from(stylesheet.element!.attributes).reduce((acc, attr) => {
+      acc[attr.name] = attr.value;
+      return acc;
+    }, {} as Record<string, string>);
+
+    expect(actualElementAttributes).toEqual(stylesheet.elementAttributes);
+  });
+
+  it('should create HTML style element', () => {
+    const stylesheet = createIsomorphicStyleSheet(document.createElement('style'), 'd', {});
+    expect(stylesheet.element).not.toBeUndefined();
+    expect(stylesheet.element).toMatchInlineSnapshot(`
+      <style
+        data-make-styles-bucket="d"
+      />
+    `);
+  });
+});
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/core/src/renderer/rehydrateRendererCache.test.ts b/packages/core/src/renderer/rehydrateRendererCache.test.ts
new file mode 100644
index 0000000..faf2c68
--- /dev/null
+++ b/packages/core/src/renderer/rehydrateRendererCache.test.ts
@@ -0,0 +1,38 @@
+import { DATA_BUCKET_ATTR } from '../constants';
+import { createDOMRenderer } from './createDOMRenderer';
+import { rehydrateRendererCache } from './rehydrateRendererCache';
+
+describe('rehydrateRendererCache', () => {
+  it('should rehydrate renderer cache', () => {
+    const renderer = createDOMRenderer(document);
+    const styleElement = document.createElement('style');
+    styleElement.setAttribute(DATA_BUCKET_ATTR, 'd');
+    document.head.appendChild(styleElement);
+    styleElement.textContent = '.foo { color: red; }';
+
+    rehydrateRendererCache(renderer, document);
+
+    expect(renderer.insertionCache).toMatchInlineSnapshot(`
+      Object {
+        ".foo { color: red; }": "d",
+      }
+    `);
+  });
+
+  it('should create isomorphic stylesheet', () => {
+    const renderer = createDOMRenderer(document);
+    const styleElement = document.createElement('style');
+    document.head.appendChild(styleElement);
+    styleElement.setAttribute(DATA_BUCKET_ATTR, 'd');
+
+    rehydrateRendererCache(renderer, document);
+
+    expect(renderer.stylesheets.d).not.toBeUndefined();
+    expect(renderer.stylesheets.d?.bucketName).toMatchInlineSnapshot(`"d"`);
+    expect(renderer.stylesheets.d?.elementAttributes).toMatchInlineSnapshot(`
+      Object {
+        "data-make-styles-bucket": "d",
+      }
+    `);
+  });
+});
diff --git a/packages/core/src/runtime/getStyleBucketName.test.ts b/packages/core/src/runtime/getStyleBucketName.test.ts
index 93239d6..052a02f 100644
--- a/packages/core/src/runtime/getStyleBucketName.test.ts
+++ b/packages/core/src/runtime/getStyleBucketName.test.ts
@@ -14,7 +14,7 @@ describe('getStyleBucketName', () => {
     expect(getStyleBucketName(':active', '', '', '')).toBe('a');
 
     expect(getStyleBucketName(':active', 'theme', '', '')).toBe('t');
-    expect(getStyleBucketName(':active', '', '(max-width: 100px)', '')).toBe('t');
+    expect(getStyleBucketName(':active', '', '(max-width: 100px)', '')).toBe('m');
     expect(getStyleBucketName(':active', '', '', '(display: table-cell)')).toBe('t');
   });
 });
diff --git a/packages/core/src/runtime/resolveStyleRules.test.ts b/packages/core/src/runtime/resolveStyleRules.test.ts
index 403698b..cf43f45 100644
--- a/packages/core/src/runtime/resolveStyleRules.test.ts
+++ b/packages/core/src/runtime/resolveStyleRules.test.ts
@@ -208,6 +208,25 @@ describe('resolveStyleRules', () => {
       `);
     });
 
+    it('handles media queries with flipping values', () => {
+      expect(
+        resolveStyleRules({
+          '@media screen and (max-width: 992px)': {
+            textAlign: 'left',
+          },
+        }),
+      ).toMatchInlineSnapshot(`
+        @media screen and (max-width: 992px) {
+          .f1f6067w {
+            text-align: left;
+          }
+          .f133ge8t {
+            text-align: right;
+          }
+        }
+      `);
+    });
+
     it('RTL @noflip will generate a different className', () => {
       const classnamesSet = new Set<string>();
 
diff --git a/packages/core/src/runtime/utils/normalizeCSSBucketEntry.test.ts b/packages/core/src/runtime/utils/normalizeCSSBucketEntry.test.ts
new file mode 100644
index 0000000..1793724
--- /dev/null
+++ b/packages/core/src/runtime/utils/normalizeCSSBucketEntry.test.ts
@@ -0,0 +1,21 @@
+import { normalizeCSSBucketEntry } from './normalizeCSSBucketEntry';
+
+describe('normalizeCSSBucketEntry', () => {
+  it('should handle string entry', () => {
+    const cssRule = '.foo { color: red; }';
+    expect(normalizeCSSBucketEntry(cssRule)).toEqual([cssRule]);
+  });
+
+  it('should handle array entry', () => {
+    const cssRule = '.foo { color: red; }';
+    expect(normalizeCSSBucketEntry([cssRule, {}])).toEqual([cssRule, {}]);
+  });
+
+  it('should throw if array entry contains more than 2 items', () => {
+    const cssRule = '.foo { color: red; }';
+    // @ts-expect-error types should disallow entries with more than 2 items
+    expect(() => normalizeCSSBucketEntry([cssRule, {}, {}])).toThrow(
+      'CSS Bucket contains an entry with greater than 2 items, please report this to https://github.com/microsoft/griffel/issues',
+    );
+  });
+});
diff --git a/packages/core/src/shorthands/flex.test.ts b/packages/core/src/shorthands/flex.test.ts
index 5c6d6fd..11f318f 100644
--- a/packages/core/src/shorthands/flex.test.ts
+++ b/packages/core/src/shorthands/flex.test.ts
@@ -98,6 +98,11 @@ describe('flex', () => {
   });
 
   it('for incorrect input', () => {
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+
     expect(flex('hello', 'oh', 'no')).toEqual({});
+
+    jest.spyOn(console, 'error').mockClear();
   });
 });
diff --git a/packages/core/src/shorthands/gridArea.test.ts b/packages/core/src/shorthands/gridArea.test.ts
new file mode 100644
index 0000000..ef2f459
--- /dev/null
+++ b/packages/core/src/shorthands/gridArea.test.ts
@@ -0,0 +1,225 @@
+import { gridArea } from './gridArea';
+
+describe('gridArea(all)', () => {
+  it('for auto', () => {
+    expect(gridArea('auto')).toEqual({
+      gridRowStart: 'auto',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for custom-ident', () => {
+    expect(gridArea('header')).toEqual({
+      gridRowStart: 'header',
+      gridColumnStart: 'header',
+      gridRowEnd: 'header',
+      gridColumnEnd: 'header',
+    });
+  });
+
+  it('for number', () => {
+    expect(gridArea(2)).toEqual({
+      gridRowStart: 2,
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span number', () => {
+    expect(gridArea('span 3')).toEqual({
+      gridRowStart: 'span 3',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span custom-ident', () => {
+    expect(gridArea('span header')).toEqual({
+      gridRowStart: 'span header',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+});
+
+describe('gridArea(rowStart, columnStart)', () => {
+  it('for auto', () => {
+    expect(gridArea('auto', 'auto')).toEqual({
+      gridRowStart: 'auto',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for custom-ident', () => {
+    expect(gridArea('header', 'footer')).toEqual({
+      gridRowStart: 'header',
+      gridColumnStart: 'footer',
+      gridRowEnd: 'header',
+      gridColumnEnd: 'footer',
+    });
+  });
+
+  it('for number', () => {
+    expect(gridArea(2, 4)).toEqual({
+      gridRowStart: 2,
+      gridColumnStart: 4,
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span number', () => {
+    expect(gridArea(3, 'span 2')).toEqual({
+      gridRowStart: 3,
+      gridColumnStart: 'span 2',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span custom-ident', () => {
+    expect(gridArea('header', 'span footer')).toEqual({
+      gridRowStart: 'header',
+      gridColumnStart: 'span footer',
+      gridRowEnd: 'header',
+      gridColumnEnd: 'auto',
+    });
+  });
+});
+
+describe('gridArea(rowStart, columnStart, rowEnd)', () => {
+  it('for auto', () => {
+    expect(gridArea('auto', 'auto', 'auto')).toEqual({
+      gridRowStart: 'auto',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for custom-ident', () => {
+    expect(gridArea(2, 1, 'footer')).toEqual({
+      gridRowStart: 2,
+      gridColumnStart: 1,
+      gridRowEnd: 'footer',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for number', () => {
+    expect(gridArea(2, 4, 3)).toEqual({
+      gridRowStart: 2,
+      gridColumnStart: 4,
+      gridRowEnd: 3,
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span number', () => {
+    expect(gridArea('header', 2, 'span 2')).toEqual({
+      gridRowStart: 'header',
+      gridColumnStart: 2,
+      gridRowEnd: 'span 2',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for span custom-ident', () => {
+    expect(gridArea('header', 'span body', 'span footer')).toEqual({
+      gridRowStart: 'header',
+      gridColumnStart: 'span body',
+      gridRowEnd: 'span footer',
+      gridColumnEnd: 'auto',
+    });
+  });
+});
+
+describe('gridArea(rowStart, columnStart, rowEnd, columnEnd)', () => {
+  it('for auto', () => {
+    expect(gridArea('auto', 'auto', 'auto', 'auto')).toEqual({
+      gridRowStart: 'auto',
+      gridColumnStart: 'auto',
+      gridRowEnd: 'auto',
+      gridColumnEnd: 'auto',
+    });
+  });
+
+  it('for custom-ident', () => {
+    expect(gridArea('a', 'b', 'c', 'd')).toEqual({
+      gridRowStart: 'a',
+      gridColumnStart: 'b',
+      gridRowEnd: 'c',
+      gridColumnEnd: 'd',
+    });
+  });
+
+  it('for number', () => {
+    expect(gridArea(2, 4, 3, 1)).toEqual({
+      gridRowStart: 2,
+      gridColumnStart: 4,
+      gridRowEnd: 3,
+      gridColumnEnd: 1,
+    });
+  });
+
+  it('for span number', () => {
+    expect(gridArea('span 1', 'span 2', 'span 3', 'span 4')).toEqual({
+      gridRowStart: 'span 1',
+      gridColumnStart: 'span 2',
+      gridRowEnd: 'span 3',
+      gridColumnEnd: 'span 4',
+    });
+  });
+
+  it('for span custom-ident', () => {
+    expect(gridArea('span header', 'span first', 'span footer', 'span last')).toEqual({
+      gridRowStart: 'span header',
+      gridColumnStart: 'span first',
+      gridRowEnd: 'span footer',
+      gridColumnEnd: 'span last',
+    });
+  });
+
+  it('for mixture', () => {
+    expect(gridArea('auto', 3, 'footer', 'span 2')).toEqual({
+      gridRowStart: 'auto',
+      gridColumnStart: 3,
+      gridRowEnd: 'footer',
+      gridColumnEnd: 'span 2',
+    });
+  });
+
+  it('for css var reference', () => {
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+
+    expect(gridArea('header', 'header', 'var(--my-var)', 'header')).toEqual({});
+
+    jest.spyOn(console, 'error').mockClear();
+  });
+
+  it('for css var reference within string', () => {
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+
+    expect(gridArea('header', 'header', 'header var(--my-var) header', 'header')).toEqual({});
+
+    jest.spyOn(console, 'error').mockClear();
+  });
+
+  it('for empty css var reference', () => {
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+
+    expect(gridArea('header', 'header', 'var()', 'header')).toEqual({});
+
+    jest.spyOn(console, 'error').mockClear();
+  });
+});
diff --git a/packages/devtools/src/getMonolithicCSSRules.test.ts b/packages/devtools/src/getMonolithicCSSRules.test.ts
index be80a8c..4fd5f80 100644
--- a/packages/devtools/src/getMonolithicCSSRules.test.ts
+++ b/packages/devtools/src/getMonolithicCSSRules.test.ts
@@ -2,46 +2,58 @@ import { getMonolithicCSSRules } from './getMonolithicCSSRules';
 
 describe('getMonolithicCSSRules', () => {
   it('group atomic css with class names selector', () => {
-    const rules = ['.abcdef0{background-color:red;}', '.abcdef1{color:red;}'].map(cssRule => ({ cssRule }));
+    const rules = ['.fabcde0{background-color:red;}', '.fabcde1{color:red;}'].map(cssRule => ({ cssRule }));
     expect(getMonolithicCSSRules(rules)).toEqual({
       '': [
         {
-          className: 'abcdef0',
+          className: 'fabcde0',
           css: 'background-color:red;',
         },
         {
-          className: 'abcdef1',
+          className: 'fabcde1',
           css: 'color:red;',
         },
       ],
     });
   });
 
+  it('group atomic css with child class names selector', () => {
+    const rules = ['.f1dx00p .class1{padding-right: 10px;}'].map(cssRule => ({ cssRule }));
+    expect(getMonolithicCSSRules(rules)).toEqual({
+      ' .class1': [
+        {
+          className: 'f1dx00p',
+          css: 'padding-right:10px;',
+        },
+      ],
+    });
+  });
+
   it('separate atomic css with class names selector and pseudo selector', () => {
     const rules = [
-      '.abcdef0{background-color:red;}',
-      '.abcdef1{display:block;}',
-      '.abcdef2:hover{display:none;}',
-      '.abcdef3:hover{color:red;}',
+      '.fabcde0{background-color:red;}',
+      '.fabcde1{display:block;}',
+      '.fabcde2:hover{display:none;}',
+      '.fabcde3:hover{color:red;}',
     ].map(cssRule => ({ cssRule }));
     expect(getMonolithicCSSRules(rules)).toEqual({
       '': [
         {
-          className: 'abcdef0',
+          className: 'fabcde0',
           css: 'background-color:red;',
         },
         {
-          className: 'abcdef1',
+          className: 'fabcde1',
           css: 'display:block;',
         },
       ],
       ':hover': [
         {
-          className: 'abcdef2',
+          className: 'fabcde2',
           css: 'display:none;',
         },
         {
-          className: 'abcdef3',
+          className: 'fabcde3',
           css: 'color:red;',
         },
       ],
@@ -50,30 +62,30 @@ describe('getMonolithicCSSRules', () => {
 
   it('group atomic css with media selector', () => {
     const rules = [
-      '.abcdef0{background-color:red;}',
-      '.abcdef1{display:block;}',
-      '.abcdef2:hover{display:none;}',
-      '.abcdef3:hover{color:red;}',
+      '.fabcde0{background-color:red;}',
+      '.fabcde1{display:block;}',
+      '.fabcde2:hover{display:none;}',
+      '.fabcde3:hover{color:red;}',
     ].map(cssRule => ({ cssRule: `@media screen and (max-width: 992px){${cssRule}}` }));
     expect(getMonolithicCSSRules(rules)).toEqual({
       '@media screen and (max-width: 992px)': {
         '': [
           {
-            className: 'abcdef0',
+            className: 'fabcde0',
             css: 'background-color:red;',
           },
           {
-            className: 'abcdef1',
+            className: 'fabcde1',
             css: 'display:block;',
           },
         ],
         ':hover': [
           {
-            className: 'abcdef2',
+            className: 'fabcde2',
             css: 'display:none;',
           },
           {
-            className: 'abcdef3',
+            className: 'fabcde3',
             css: 'color:red;',
           },
         ],
@@ -83,21 +95,21 @@ describe('getMonolithicCSSRules', () => {
 
   it('separate atomic css with different media selector', () => {
     const rules = [
-      '.abcdef0{background-color:red;}',
-      '@media screen and (max-width: 992px){.abcdef0{background-color:blue;}}',
-      '@media screen and (max-width: 1024px){.abcdef0{background-color:green;}}',
+      '.fabcde0{background-color:red;}',
+      '@media screen and (max-width: 992px){.fabcde0{background-color:blue;}}',
+      '@media screen and (max-width: 1024px){.fabcde0{background-color:green;}}',
     ].map(cssRule => ({ cssRule }));
     expect(getMonolithicCSSRules(rules)).toEqual({
       '': [
         {
-          className: 'abcdef0',
+          className: 'fabcde0',
           css: 'background-color:red;',
         },
       ],
       '@media screen and (max-width: 1024px)': {
         '': [
           {
-            className: 'abcdef0',
+            className: 'fabcde0',
             css: 'background-color:green;',
           },
         ],
@@ -105,7 +117,7 @@ describe('getMonolithicCSSRules', () => {
       '@media screen and (max-width: 992px)': {
         '': [
           {
-            className: 'abcdef0',
+            className: 'fabcde0',
             css: 'background-color:blue;',
           },
         ],
@@ -115,21 +127,21 @@ describe('getMonolithicCSSRules', () => {
 
   it('pass on overriddenBy information from atomic css to monolithic css', () => {
     const rules = [
-      { cssRule: '.abcdef0{background-color:red;}', overriddenBy: 'bcdefg0' },
-      { cssRule: '.abcdef0>div{color:green;}', overriddenBy: 'bcdefg1' },
-      { cssRule: '@media screen and (max-width: 992px){.abcdef0{background-color:blue;}}', overriddenBy: 'bcdefg2' },
+      { cssRule: '.fabcde0{background-color:red;}', overriddenBy: 'bcdefg0' },
+      { cssRule: '.fabcde0>div{color:green;}', overriddenBy: 'bcdefg1' },
+      { cssRule: '@media screen and (max-width: 992px){.fabcde0{background-color:blue;}}', overriddenBy: 'bcdefg2' },
     ];
     expect(getMonolithicCSSRules(rules)).toEqual({
       '': [
         {
-          className: 'abcdef0',
+          className: 'fabcde0',
           css: 'background-color:red;',
           overriddenBy: 'bcdefg0',
         },
       ],
       '>div': [
         {
-          className: 'abcdef0',
+          className: 'fabcde0',
           css: 'color:green;',
           overriddenBy: 'bcdefg1',
         },
@@ -137,7 +149,7 @@ describe('getMonolithicCSSRules', () => {
       '@media screen and (max-width: 992px)': {
         '': [
           {
-            className: 'abcdef0',
+            className: 'fabcde0',
             css: 'background-color:blue;',
             overriddenBy: 'bcdefg2',
           },
@@ -145,4 +157,35 @@ describe('getMonolithicCSSRules', () => {
       },
     });
   });
+
+  it('group atomic css when class name selector has selector in front', () => {
+    const rules = [
+      '[data-keyboard-nav] .fabcde0:focus{background-color:red;}',
+      '[data-keyboard-nav] .fabcde1{color:red;}',
+      '[data-keyboard-nav] .fabcde1 .class1{padding-top:10px;}',
+    ].map(cssRule => ({ cssRule }));
+    expect(getMonolithicCSSRules(rules)).toEqual({
+      '[data-keyboard-nav] ': [
+        {
+          className: 'fabcde1',
+          css: 'color:red;',
+          overriddenBy: undefined,
+        },
+      ],
+      '[data-keyboard-nav]  .class1': [
+        {
+          className: 'fabcde1',
+          css: 'padding-top:10px;',
+          overriddenBy: undefined,
+        },
+      ],
+      '[data-keyboard-nav] :focus': [
+        {
+          className: 'fabcde0',
+          css: 'background-color:red;',
+          overriddenBy: undefined,
+        },
+      ],
+    });
+  });
 });
diff --git a/packages/react/src/renderToStyleElements-node.test.tsx b/packages/react/src/renderToStyleElements-node.test.tsx
index 6f66fa1..3a6d875 100644
--- a/packages/react/src/renderToStyleElements-node.test.tsx
+++ b/packages/react/src/renderToStyleElements-node.test.tsx
@@ -69,11 +69,22 @@ describe('renderToStyleElements', () => {
           ':hover': { color: 'blue' },
         },
       },
+
+      support: {
+        '@supports (display: grid)': {
+          color: 'red',
+        },
+      },
     });
     const ExampleComponent: React.FC = () => {
       const classes = useExampleStyles();
 
-      return <div className={classes.media} />;
+      return (
+        <>
+          <div className={classes.media} />
+          <div className={classes.support} />
+        </>
+      );
     };
 
     const renderer = createDOMRenderer();
@@ -86,6 +97,16 @@ describe('renderToStyleElements', () => {
 
     expect(ReactDOM.renderToStaticMarkup(<>{renderToStyleElements(renderer)}</>)).toMatchInlineSnapshot(`
       <style data-make-styles-bucket="t" data-make-styles-rehydration="true">
+        @supports (display: grid) {
+          .f1ofq0jl {
+            color: red;
+          }
+        }</style
+      ><style
+        media="screen and (max-width: 992px)"
+        data-make-styles-bucket="m"
+        data-make-styles-rehydration="true"
+      >
         @media screen and (max-width: 992px) {
           .fnao3vb:hover {
             color: blue;
@@ -95,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/react/src/renderToStyleElements.test.tsx b/packages/react/src/renderToStyleElements.test.tsx
new file mode 100644
index 0000000..cf85399
--- /dev/null
+++ b/packages/react/src/renderToStyleElements.test.tsx
@@ -0,0 +1,55 @@
+import { createDOMRenderer } from '@griffel/core';
+import * as prettier from 'prettier';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/server';
+
+import { makeStyles } from './makeStyles';
+import { RendererProvider } from './RendererContext';
+import { renderToStyleElements } from './renderToStyleElements';
+
+expect.addSnapshotSerializer({
+  test(value) {
+    return typeof value === 'string';
+  },
+  print(value) {
+    /**
+     * test function makes sure that value is the guarded type
+     */
+    const _value = value as string;
+
+    return prettier.format(_value, { parser: 'html' }).trim();
+  },
+});
+
+describe('renderToStyleElements', () => {
+  it('renders elements in DOM env', () => {
+    const useExampleStyles = makeStyles({
+      root: { color: 'red', ':hover': { color: 'green' } },
+    });
+    const ExampleComponent: React.FC = () => {
+      const classes = useExampleStyles();
+
+      return <div className={classes.root} />;
+    };
+
+    const renderer = createDOMRenderer();
+
+    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="h" data-make-styles-rehydration="true">
+        .f1ej289o:hover {
+          color: green;
+        }
+      </style>
+    `);
+  });
+});
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..5848f41
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
@@ -0,0 +1,224 @@
+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 = {
+  cssFilename?: string;
+  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({
+        filename: options.cssFilename ?? '[name].css',
+      }),
+    ],
+
+    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 filesList = virtualFsVolume.readdirSync(__dirname) as string[];
+
+      const cssFilePath = path.resolve(
+        __dirname,
+        options.cssFilename ? filesList.find(filename => filename.includes('griffel')) ?? '' : '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;
+
+      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');
+
+  // Multiple calls of __styles
+  testFixture('multiple');
+
+  // Deduplicate rules in stylesheet
+  testFixture('rules-deduplication');
+
+  // Sorting rules by buckets
+  testFixture('style-buckets');
+
+  // Custom filenames in mini-css-extract-plugin
+  testFixture('config-name', { cssFilename: '[name].[contenthash].css' });
+
+  // "pathinfo" adds comments with paths to output
+  testFixture('basic-rules', { webpackConfig: { output: { pathinfo: true } } });
+});
diff --git a/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.test.ts b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.test.ts
new file mode 100644
index 0000000..c29bb93
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.test.ts
@@ -0,0 +1,36 @@
+import pluginTester, { prettierFormatter } from 'babel-plugin-tester';
+import * as fs from 'fs';
+import * as path from 'path';
+
+import { babelPluginStripGriffelRuntime } from './babelPluginStripGriffelRuntime';
+
+const prettierConfig = JSON.parse(
+  fs.readFileSync(path.resolve(__dirname, '../../../.prettierrc'), { encoding: 'utf-8' }),
+);
+const fixturesDir = path.join(__dirname, '..', '__fixtures__', 'babel');
+
+pluginTester({
+  babelOptions: {
+    parserOpts: {
+      plugins: ['typescript'],
+    },
+  },
+  pluginOptions: {
+    babelOptions: {
+      presets: ['@babel/typescript'],
+    },
+  },
+  formatResult: code =>
+    prettierFormatter(code, {
+      config: {
+        ...prettierConfig,
+        parser: 'typescript',
+      },
+    }),
+
+  fixtures: fixturesDir,
+  tests: [],
+
+  plugin: babelPluginStripGriffelRuntime,
+  pluginName: '@griffel/webpack-extraction-plugin/babel',
+});
diff --git a/packages/webpack-extraction-plugin/src/sortCSSRules.test.ts b/packages/webpack-extraction-plugin/src/sortCSSRules.test.ts
new file mode 100644
index 0000000..2760007
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/sortCSSRules.test.ts
@@ -0,0 +1,153 @@
+import { GriffelRenderer } from '@griffel/core';
+import * as prettier from 'prettier';
+import { compile } from 'stylis';
+
+import { getElementReference, getElementMetadata, sortCSSRules } from './sortCSSRules';
+
+export const cssSerializer: jest.SnapshotSerializerPlugin = {
+  test(value) {
+    return typeof value === 'string';
+  },
+  print(value) {
+    /**
+     * test function makes sure that value is the guarded type
+     */
+    const _value = value as string;
+
+    return prettier.format(_value, { parser: 'css' }).trim();
+  },
+};
+
+expect.addSnapshotSerializer(cssSerializer);
+
+describe('getElementReference', () => {
+  it.each`
+    css                                                                                              | reference
+    ${'.foo { color: red; }'}                                                                        | ${'.foo'}
+    ${'.foo:hover { color: red; }'}                                                                  | ${'.foo:hover'}
+    ${'@media (max-width: 2px) { .foo { color: blue; } }'}                                           | ${'@media (max-width: 2px)[.foo]'}
+    ${'@keyframes foo { from { transform:rotate(0deg); } to { transform:rotate(360deg); } }'}        | ${'@keyframes foo'}
+    ${'@media screen and (max-width: 992px) { .a { text-align: left; } .b { text-align: right; } }'} | ${'@media screen and (max-width: 992px)[.a,.b]'}
+    ${'@media (max-width: 2px) { @supports (display: grid) { .a { color: blue; } } }'}               | ${'@media (max-width: 2px)[@supports (display: grid)[.a]]'}
+  `('returns "$reference" for "$css"', ({ css, reference }: { css: string; reference: string }) => {
+    const element = compile(css)[0];
+
+    expect(getElementReference(element)).toBe(reference);
+  });
+});
+
+describe('getElementMetadata', () => {
+  it.each`
+    css                                                    | metadata
+    ${'.foo { color: red; }'}                              | ${''}
+    ${'.foo:hover { color: red; }'}                        | ${''}
+    ${'@media (max-width: 2px) { .foo { color: blue; } }'} | ${'(max-width: 2px)'}
+  `('returns "$reference" for "$css"', ({ css, metadata }: { css: string; metadata: string }) => {
+    const element = compile(css)[0];
+
+    expect(getElementMetadata(element)).toBe(metadata);
+  });
+});
+
+describe('sortCSSRules', () => {
+  it('removes duplicate rules', () => {
+    const css = `
+      .baz { color: orange; }
+      .foo { color: red; }
+      .baz { color: orange; }
+
+      @media (max-width: 2px) { .foo { color: blue; } }
+      @media (max-width: 2px) { .yellow { color: blue; } }
+    `;
+
+    expect(sortCSSRules(css, () => 0)).toMatchInlineSnapshot(`
+      .baz {
+        color: orange;
+      }
+      .foo {
+        color: red;
+      }
+      @media (max-width: 2px) {
+        .foo {
+          color: blue;
+        }
+      }
+      @media (max-width: 2px) {
+        .yellow {
+          color: blue;
+        }
+      }
+    `);
+  });
+
+  it('sorts rules by buckets order', () => {
+    const css = `
+      .foo:focus { color: pink; }
+      .baz { color: orange; }
+      .foo:hover { color: yellow; }
+      .foo { color: red; }
+    `;
+
+    expect(sortCSSRules(css, () => 0)).toMatchInlineSnapshot(`
+      .baz {
+        color: orange;
+      }
+      .foo {
+        color: red;
+      }
+      .foo:focus {
+        color: pink;
+      }
+      .foo:hover {
+        color: yellow;
+      }
+    `);
+  });
+
+  it('sorts media queries', () => {
+    const css = `
+      @media (max-width: 2px) { .foo { color: blue; } }
+      @media (max-width: 1px) { .foo { color: red; } }
+      @media (max-width: 3px) { .foo { color: red; } }
+      .foo { color: green; }
+    `;
+
+    const mediaQueryOrder = ['(max-width: 1px)', '(max-width: 2px)', '(max-width: 3px)', '(max-width: 4px)'];
+    const compareMediaQueries: GriffelRenderer['compareMediaQueries'] = (a: string, b: string) =>
+      mediaQueryOrder.indexOf(a) - mediaQueryOrder.indexOf(b);
+
+    expect(sortCSSRules(css, compareMediaQueries)).toMatchInlineSnapshot(`
+      .foo {
+        color: green;
+      }
+      @media (max-width: 1px) {
+        .foo {
+          color: red;
+        }
+      }
+      @media (max-width: 2px) {
+        .foo {
+          color: blue;
+        }
+      }
+      @media (max-width: 3px) {
+        .foo {
+          color: red;
+        }
+      }
+    `);
+  });
+
+  it('removes comments from output', () => {
+    const css = `
+      /* This is a comment in CSS */
+      .baz { color: orange; }
+    `;
+
+    expect(sortCSSRules(css, () => 0)).toMatchInlineSnapshot(`
+      .baz {
+        color: orange;
+      }
+    `);
+  });
+});
diff --git a/packages/webpack-extraction-plugin/src/webpackLoader.test.ts b/packages/webpack-extraction-plugin/src/webpackLoader.test.ts
new file mode 100644
index 0000000..c2ad245
--- /dev/null
+++ b/packages/webpack-extraction-plugin/src/webpackLoader.test.ts
@@ -0,0 +1,187 @@
+import * as fs from 'fs';
+import { createFsFromVolume, Volume } from 'memfs';
+import * as path from 'path';
+import * as prettier from 'prettier';
+import * as webpack from 'webpack';
+import { merge } from 'webpack-merge';
+
+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<{ moduleSource: 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|txt)$/,
+          include: path.dirname(entryPath),
+          use: {
+            loader: path.resolve(__dirname, './webpackLoader.ts'),
+          },
+        },
+        {
+          test: /\.css$/,
+          use: 'css-loader',
+        },
+      ],
+    },
+
+    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;
+      }
+
+      const jsonStats = stats.toJson({ source: true });
+
+      if (stats.hasErrors()) {
+        reject(stats.toJson().errors![0]);
+        return;
+      }
+
+      if (!Array.isArray(jsonStats.modules)) {
+        reject(new Error(`"stats.toJson().modules" should be an array, this could be a compilation error...`));
+        return;
+      }
+
+      const entryModule = jsonStats.modules.find(module => module.nameForCondition === entryPath);
+
+      if (!entryModule) {
+        reject(new Error(`Failed to find a fixture in "stats.toJson().modules", this could be a compilation error...`));
+        return;
+      }
+
+      resolve({ moduleSource: entryModule.source as string });
+    });
+  });
+}
+
+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');
+    // Specially for cases when "code" contains syntax errors
+    const txtCodePath = path.resolve(fixturePath, 'code.txt');
+
+    const tsOutputPath = path.resolve(fixturePath, 'output.ts');
+    const tsxOutputPath = path.resolve(fixturePath, 'output.tsx');
+
+    const inputPath = [
+      fs.existsSync(tsCodePath) && tsCodePath,
+      fs.existsSync(tsxCodePath) && tsxCodePath,
+      fs.existsSync(txtCodePath) && txtCodePath,
+    ].find(Boolean);
+    const outputPath = [
+      fs.existsSync(tsOutputPath) && tsOutputPath,
+      fs.existsSync(tsxOutputPath) && tsxOutputPath,
+    ].find(Boolean);
+
+    const errorPath = path.resolve(fixturePath, 'error.ts');
+    const expectedError = fs.existsSync(errorPath) && require(errorPath);
+
+    if (!inputPath) {
+      throw new Error(`Failed to find "code.{js,ts,tsx}" in "${fixturePath}"`);
+    }
+
+    if (!outputPath && !expectedError) {
+      throw new Error(`Failed to find "output.{js,ts,tsx}" 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 resultModule = '';
+    let resultError: Error | webpack.StatsError = new Error();
+
+    try {
+      const result = await compileSourceWithWebpack(inputPath, options);
+
+      resultModule = fixLineEndings(
+        prettier.format(result.moduleSource, {
+          ...prettierConfig,
+          parser: 'typescript',
+        }),
+      );
+    } catch (err) {
+      if (expectedError) {
+        resultError = err as webpack.StatsError;
+      } else {
+        throw err;
+      }
+    }
+
+    if (outputPath) {
+      const moduleOutput = fixLineEndings(await fs.promises.readFile(outputPath, { encoding: 'utf8' }));
+
+      expect(resultModule).toBe(moduleOutput);
+      return;
+    }
+
+    if (expectedError) {
+      expect(resultError.message).toMatch(expectedError.default);
+    }
+  }, 15000);
+}
+
+describe('webpackLoader', () => {
+  // Basic assertions
+  testFixture('basic-rules');
+
+  // Multiple
+  testFixture('multiple');
+});