Repository: microsoft/griffel
Pull request #454 (2023-10-17 09:57:40)

feat(webpack-extraction-plugin): add compat for Rspack
This PR updates `@griffel/webpack-extraction-plugin` to support Rspack.

----

### Changes to E2E

- New E2E added for Rspack
- Improved snapshot diff in E2E tests

### Changes to `@griffel/webpack-loader`

No changes need, works as it is

### Changes to `@griffel/webpack-extraction-plugin`

All specific places are marked with `@ Rspack compat` comment.

The biggest problem is that Rspack does not support functions for `test` in `splitChunks.cacheGroups`. Because of that we can't detect generated CSS modules via `isCSSModule()` 😭 That's the reason why I have switched for Rspack to the previous approach with a fake CSS file as then we can use a regex for `test` assertion. All other changes are related this problem.


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

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

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/change/@griffel-webpack-extraction-plugin-461846c0-9280-4df4-97ef-34192aad11df.json b/change/@griffel-webpack-extraction-plugin-461846c0-9280-4df4-97ef-34192aad11df.json
new file mode 100644
index 0000000..9e8b340
--- /dev/null
+++ b/change/@griffel-webpack-extraction-plugin-461846c0-9280-4df4-97ef-34192aad11df.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat(webpack-extraction-plugin): add compat for Rspack",
+  "packageName": "@griffel/webpack-extraction-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/package.json b/package.json
index 0c9bf05..8915a05 100644
--- a/package.json
+++ b/package.json
@@ -124,6 +124,7 @@
     "react-shadow": "^20.2.0",
     "react-test-renderer": "18.0.0",
     "simple-git-hooks": "2.7.0",
+    "snapshot-diff": "0.10.0",
     "styled-components": "^5.3.6",
     "stylelint": "^15.10.3",
     "synckit": "^0.8.5",
@@ -148,6 +149,7 @@
     "@emotion/hash": "^0.9.0",
     "@linaria/babel-preset": "^3.0.0-beta.24",
     "@linaria/shaker": "^3.0.0-beta.22",
+    "@rspack/cli": "^0.3.6",
     "@swc/core": "^1.3.19",
     "@typescript-eslint/utils": "^5.47.0",
     "ajv": "^8.4.0",
diff --git a/packages/webpack-extraction-plugin/project.json b/packages/webpack-extraction-plugin/project.json
index 2fad9e9..3965999 100644
--- a/packages/webpack-extraction-plugin/project.json
+++ b/packages/webpack-extraction-plugin/project.json
@@ -36,7 +36,7 @@
             "output": "."
           },
           {
-            "glob": "*.js",
+            "glob": "*.(css|js)",
             "input": "packages/webpack-extraction-plugin/virtual-loader",
             "output": "./virtual-loader"
           }
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
index 0a8dffe..8105bb5 100644
--- a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.ts
@@ -106,6 +106,8 @@ export class GriffelCSSExtractionPlugin {
   }
 
   apply(compiler: Compiler): void {
+    const IS_RSPACK = Object.prototype.hasOwnProperty.call(compiler.webpack, 'rspackVersion');
+
     // WHAT?
     //   Prevents ".griffel.css" files from being tree shaken by forcing "sideEffects" setting.
     // WHY?
@@ -113,17 +115,23 @@ export class GriffelCSSExtractionPlugin {
     //   will have paths relative to source file. To identify what files have side effects Webpack relies on
     //   "sideEffects" field in "package.json" and NPM packages usually have "sideEffects: false" that will trigger
     //   Webpack to shake out generated CSS.
-    compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, nmf => {
-      nmf.hooks.createModule.tap(
-        PLUGIN_NAME,
-        // @ts-expect-error CreateData is typed as 'object'...
-        (createData: { matchResource?: string; settings: { sideEffects?: boolean } }) => {
-          if (createData.matchResource && createData.matchResource.endsWith('.griffel.css')) {
-            createData.settings.sideEffects = true;
-          }
-        },
-      );
-    });
+
+    // @ Rspack compat:
+    // "createModule" in "normalModuleFactory" is not supported by Rspack
+    // https://github.com/web-infra-dev/rspack/blob/e52601e059fff1f0cdc4e9328746fb3ae6c3ecb2/packages/rspack/src/NormalModuleFactory.ts#L53
+    if (!IS_RSPACK) {
+      compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, nmf => {
+        nmf.hooks.createModule.tap(
+          PLUGIN_NAME,
+          // @ts-expect-error CreateData is typed as 'object'...
+          (createData: { matchResource?: string; settings: { sideEffects?: boolean } }) => {
+            if (createData.matchResource && createData.matchResource.endsWith('.griffel.css')) {
+              createData.settings.sideEffects = true;
+            }
+          },
+        );
+      });
+    }
 
     // WHAT?
     //  Forces all modules emitted by an extraction loader to be moved in a single chunk by SplitChunksPlugin config.
@@ -134,58 +142,85 @@ export class GriffelCSSExtractionPlugin {
       compiler.options.optimization.splitChunks.cacheGroups ??= {};
       compiler.options.optimization.splitChunks.cacheGroups['griffel'] = {
         name: 'griffel',
-        test: isGriffelCSSModule,
+        // @ Rspack compat:
+        // Rspack does not support functions in test due performance concerns
+        // https://github.com/web-infra-dev/rspack/issues/3425#issuecomment-1577890202
+        test: IS_RSPACK ? /griffel\.css/ : isGriffelCSSModule,
         chunks: 'all',
         enforce: true,
       };
     }
 
     compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
+      // @ Rspack compat
+      // As Rspack does not support functions in "splitChunks.cacheGroups" we have to emit modules differently
+      // and can't rely on this approach due
+      if (!IS_RSPACK) {
+        // WHAT?
+        //   Adds a callback to the loader context
+        // WHY?
+        //   Allows us to register the CSS extracted from Griffel calls to then process in a CSS module
+        const cssByModuleMap = new Map<string, string>();
+
+        NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext, module) => {
+          const resourcePath = module.resource;
+
+          (loaderContext as SupplementedLoaderContext)[GriffelCssLoaderContextKey] = {
+            registerExtractedCss(css: string) {
+              cssByModuleMap.set(resourcePath, css);
+            },
+            getExtractedCss() {
+              const css = cssByModuleMap.get(resourcePath) ?? '';
+              cssByModuleMap.delete(resourcePath);
+
+              return css;
+            },
+          };
+        });
+      }
+
       // WHAT?
-      //   Adds a callback to the loader context
+      //   Performs module movements between chunks if SplitChunksPlugin is not enabled.
       // WHY?
-      //   Allows us to register the CSS extracted from Griffel calls to then process in a CSS module
-      const cssByModuleMap = new Map<string, string>();
-
-      NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext, module) => {
-        const resourcePath = module.resource;
-
-        (loaderContext as SupplementedLoaderContext)[GriffelCssLoaderContextKey] = {
-          registerExtractedCss(css: string) {
-            cssByModuleMap.set(resourcePath, css);
-          },
-          getExtractedCss() {
-            const css = cssByModuleMap.get(resourcePath) ?? '';
-            cssByModuleMap.delete(resourcePath);
-
-            return css;
-          },
-        };
-      });
+      //   The same reason as for SplitChunksPlugin config.
+      if (!compiler.options.optimization.splitChunks) {
+        // @ Rspack compat
+        // Rspack does not support adding chunks in the same as Webpack, we force usage of "optimization.splitChunks"
+        if (IS_RSPACK) {
+          throw new Error(
+            [
+              'You are using Rspack, but don\'t have "optimization.splitChunks" enabled.',
+              '"optimization.splitChunks" should be enabled for "@griffel/webpack-extraction-plugin" to function properly.',
+            ].join(' '),
+          );
+        }
 
-      compilation.hooks.optimizeChunks.tap({ name: PLUGIN_NAME, stage: OPTIMIZE_CHUNKS_STAGE_ADVANCED }, () => {
-        // WHAT?
-        //   Performs module movements between chunks if SplitChunksPlugin is not enabled.
-        // WHY?
-        //   The same reason as for SplitChunksPlugin config.
-        if (!compiler.options.optimization.splitChunks) {
+        compilation.hooks.optimizeChunks.tap({ name: PLUGIN_NAME, stage: OPTIMIZE_CHUNKS_STAGE_ADVANCED }, () => {
           moveCSSModulesToGriffelChunk(compilation);
+        });
+      }
+
+      // WHAT?
+      //  Disconnects Griffel chunk from other chunks, so Griffel chunk cannot be loaded async. Also connects with
+      //  the main entrypoint in config.
+      // WHY?
+      //  This is scenario required by one of MS teams. Will be removed in the future.
+      if (this.attachToMainEntryPoint) {
+        // @ Rspack compat
+        // We don't support this scenario for Rspack yet.
+        if (IS_RSPACK) {
+          throw new Error('You are using Rspack, "attachToMainEntryPoint" option is supported only with Webpack.');
         }
 
-        // WHAT?
-        //  Disconnects Griffel chunk from other chunks, so Griffel chunk cannot be loaded async. Also connects with
-        //  the main entrypoint in config.
-        // WHY?
-        //  This is scenario required by one of MS teams. Will be removed in the future.
-        if (this.attachToMainEntryPoint) {
+        compilation.hooks.optimizeChunks.tap({ name: PLUGIN_NAME, stage: OPTIMIZE_CHUNKS_STAGE_ADVANCED }, () => {
           const griffelChunk = compilation.namedChunks.get('griffel');
 
           if (typeof griffelChunk !== 'undefined') {
             griffelChunk.disconnectFromGroups();
             attachGriffelChunkToMainEntryPoint(compilation, griffelChunk);
           }
-        }
-      });
+        });
+      }
 
       // WHAT?
       //  Takes a CSS file from Griffel chunks and sorts CSS inside it.
@@ -195,14 +230,24 @@ export class GriffelCSSExtractionPlugin {
           stage: Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS,
         },
         assets => {
-          const griffelChunk = compilation.namedChunks.get('griffel');
-
-          if (typeof griffelChunk === 'undefined') {
-            return;
+          let cssAssetDetails;
+
+          // @ Rspack compat
+          // "compilation.namedChunks.get()" explodes with Rspack
+          if (IS_RSPACK) {
+            cssAssetDetails = Object.entries(assets).find(
+              ([assetName]) => assetName.endsWith('.css') && assetName.includes('griffel'),
+            );
+          } else {
+            const griffelChunk = compilation.namedChunks.get('griffel');
+
+            if (typeof griffelChunk === 'undefined') {
+              return;
+            }
+
+            cssAssetDetails = Object.entries(assets).find(([assetName]) => griffelChunk.files.has(assetName));
           }
 
-          const cssAssetDetails = Object.entries(assets).find(([assetName]) => griffelChunk.files.has(assetName));
-
           if (typeof cssAssetDetails === 'undefined') {
             return;
           }
diff --git a/packages/webpack-extraction-plugin/src/webpackLoader.ts b/packages/webpack-extraction-plugin/src/webpackLoader.ts
index 5f40849..c700422 100644
--- a/packages/webpack-extraction-plugin/src/webpackLoader.ts
+++ b/packages/webpack-extraction-plugin/src/webpackLoader.ts
@@ -15,6 +15,7 @@ export type WebpackLoaderOptions = {
 type WebpackLoaderParams = Parameters<webpack.LoaderDefinitionFunction<WebpackLoaderOptions>>;
 
 const virtualLoaderPath = path.resolve(__dirname, '..', 'virtual-loader', 'index.js');
+const virtualCSSFilePath = path.resolve(__dirname, '..', 'virtual-loader', 'griffel.css');
 
 /**
  * Webpack can also pass sourcemaps as a string, Babel accepts only objects.
@@ -32,6 +33,10 @@ function parseSourceMap(inputSourceMap: WebpackLoaderParams[1]): TransformOption
   }
 }
 
+function toURIComponent(rule: string): string {
+  return encodeURIComponent(rule).replace(/!/g, '%21');
+}
+
 function webpackLoader(
   this: SupplementedLoaderContext<WebpackLoaderOptions>,
   sourceCode: WebpackLoaderParams[0],
@@ -48,8 +53,14 @@ function webpackLoader(
     return;
   }
 
-  if (!this[GriffelCssLoaderContextKey]) {
-    throw new Error('GriffelCSSExtractionPlugin is not configured, please check your webpack config');
+  const IS_RSPACK = !this.webpack;
+
+  // @ Rspack compat:
+  // We don't use the trick with loader context as assets are generated differently
+  if (!IS_RSPACK) {
+    if (!this[GriffelCssLoaderContextKey]) {
+      throw new Error('GriffelCSSExtractionPlugin is not configured, please check your webpack config');
+    }
   }
 
   const { unstable_keepOriginalCode } = this.getOptions();
@@ -108,7 +119,15 @@ function webpackLoader(
         );
       }, '');
 
-      this[GriffelCssLoaderContextKey].registerExtractedCss(css);
+      if (IS_RSPACK) {
+        const request = `griffel.css!=!${virtualLoaderPath}!${virtualCSSFilePath}?style=${toURIComponent(css)}`;
+        const stringifiedRequest = JSON.stringify(this.utils.contextify(this.context || this.rootContext, request));
+
+        this.callback(null, `${resultCode}\n\nimport ${stringifiedRequest};`, resultSourceMap);
+        return;
+      }
+
+      this[GriffelCssLoaderContextKey]?.registerExtractedCss(css);
 
       const outputFileName = this.resourcePath.replace(/\.[^.]+$/, '.griffel.css');
       const request = `${outputFileName}!=!${virtualLoaderPath}!${this.resourcePath}`;
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..d87ad61
--- /dev/null
+++ b/packages/webpack-extraction-plugin/virtual-loader/griffel.css
@@ -0,0 +1,1 @@
+/** A fake CSS file, used for Rspack compat */
diff --git a/packages/webpack-extraction-plugin/virtual-loader/index.js b/packages/webpack-extraction-plugin/virtual-loader/index.js
index e3574ad..3c0290a 100644
--- a/packages/webpack-extraction-plugin/virtual-loader/index.js
+++ b/packages/webpack-extraction-plugin/virtual-loader/index.js
@@ -1,3 +1,4 @@
+const { URLSearchParams } = require('url');
 const { GriffelCssLoaderContextKey } = require('../src/constants');
 
 /**
@@ -5,7 +6,14 @@ const { GriffelCssLoaderContextKey } = require('../src/constants');
  * @return {String}
  */
 function virtualLoader() {
-  return this[GriffelCssLoaderContextKey]?.getExtractedCss() ?? '';
+  if (this.webpack) {
+    return this[GriffelCssLoaderContextKey]?.getExtractedCss() ?? '';
+  }
+
+  const query = new URLSearchParams(this.resourceQuery);
+  const style = query.get('style');
+
+  return style ?? '';
 }
 
 module.exports = virtualLoader;
diff --git a/yarn.lock b/yarn.lock
index 69eb5e2..dd113cf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -438,10 +438,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.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
-  version: 7.20.2
-  resolution: "@babel/helper-plugin-utils@npm:7.20.2"
-  checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b
+"@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.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
+  version: 7.22.5
+  resolution: "@babel/helper-plugin-utils@npm:7.22.5"
+  checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5
   languageName: node
   linkType: hard
 
@@ -960,14 +960,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-jsx@npm:^7.17.12, @babel/plugin-syntax-jsx@npm:^7.18.6":
-  version: 7.18.6
-  resolution: "@babel/plugin-syntax-jsx@npm:7.18.6"
+"@babel/plugin-syntax-jsx@npm:^7.17.12, @babel/plugin-syntax-jsx@npm:^7.18.6, @babel/plugin-syntax-jsx@npm:^7.7.2":
+  version: 7.22.5
+  resolution: "@babel/plugin-syntax-jsx@npm:7.22.5"
   dependencies:
-    "@babel/helper-plugin-utils": ^7.18.6
+    "@babel/helper-plugin-utils": ^7.22.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 6d37ea972970195f1ffe1a54745ce2ae456e0ac6145fae9aa1480f297248b262ea6ebb93010eddb86ebfacb94f57c05a1fc5d232b9a67325b09060299d515c67
+  checksum: 8829d30c2617ab31393d99cec2978e41f014f4ac6f01a1cecf4c4dd8320c3ec12fdc3ce121126b2d8d32f6887e99ca1a0bad53dedb1e6ad165640b92b24980ce
   languageName: node
   linkType: hard
 
@@ -2177,10 +2177,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@discoveryjs/json-ext@npm:^0.5.3":
-  version: 0.5.6
-  resolution: "@discoveryjs/json-ext@npm:0.5.6"
-  checksum: e97df618511fb202dffa2eb0d23e17dfb02943a70e5bc38f6b9603ad1cb1d6b525aa2b07ff9fb00b041abe425b341146ddd9e487f1e35ddadc8c6b8c56358ae0
+"@discoveryjs/json-ext@npm:^0.5.3, @discoveryjs/json-ext@npm:^0.5.7":
+  version: 0.5.7
+  resolution: "@discoveryjs/json-ext@npm:0.5.7"
+  checksum: 2176d301cc258ea5c2324402997cf8134ebb212469c0d397591636cea8d3c02f2b3cf9fd58dcb748c7a0dade77ebdc1b10284fa63e608c033a1db52fddc69918
   languageName: node
   linkType: hard
 
@@ -3030,6 +3030,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jest/expect-utils@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "@jest/expect-utils@npm:29.7.0"
+  dependencies:
+    jest-get-type: ^29.6.3
+  checksum: 75eb177f3d00b6331bcaa057e07c0ccb0733a1d0a1943e1d8db346779039cb7f103789f16e502f888a3096fb58c2300c38d1f3748b36a7fa762eb6f6d1b160ed
+  languageName: node
+  linkType: hard
+
 "@jest/expect@npm:^28.1.3":
   version: 28.1.3
   resolution: "@jest/expect@npm:28.1.3"
@@ -3150,6 +3159,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jest/schemas@npm:^29.6.3":
+  version: 29.6.3
+  resolution: "@jest/schemas@npm:29.6.3"
+  dependencies:
+    "@sinclair/typebox": ^0.27.8
+  checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93
+  languageName: node
+  linkType: hard
+
 "@jest/source-map@npm:^28.1.2":
   version: 28.1.2
   resolution: "@jest/source-map@npm:28.1.2"
@@ -3243,6 +3261,29 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jest/transform@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "@jest/transform@npm:29.7.0"
+  dependencies:
+    "@babel/core": ^7.11.6
+    "@jest/types": ^29.6.3
+    "@jridgewell/trace-mapping": ^0.3.18
+    babel-plugin-istanbul: ^6.1.1
+    chalk: ^4.0.0
+    convert-source-map: ^2.0.0
+    fast-json-stable-stringify: ^2.1.0
+    graceful-fs: ^4.2.9
+    jest-haste-map: ^29.7.0
+    jest-regex-util: ^29.6.3
+    jest-util: ^29.7.0
+    micromatch: ^4.0.4
+    pirates: ^4.0.4
+    slash: ^3.0.0
+    write-file-atomic: ^4.0.2
+  checksum: 0f8ac9f413903b3cb6d240102db848f2a354f63971ab885833799a9964999dd51c388162106a807f810071f864302cdd8e3f0c241c29ce02d85a36f18f3f40ab
+  languageName: node
+  linkType: hard
+
 "@jest/types@npm:^26.6.2":
   version: 26.6.2
   resolution: "@jest/types@npm:26.6.2"
@@ -3270,6 +3311,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jest/types@npm:^29.6.3":
+  version: 29.6.3
+  resolution: "@jest/types@npm:29.6.3"
+  dependencies:
+    "@jest/schemas": ^29.6.3
+    "@types/istanbul-lib-coverage": ^2.0.0
+    "@types/istanbul-reports": ^3.0.0
+    "@types/node": "*"
+    "@types/yargs": ^17.0.8
+    chalk: ^4.0.0
+  checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc
+  languageName: node
+  linkType: hard
+
 "@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2":
   version: 0.3.2
   resolution: "@jridgewell/gen-mapping@npm:0.3.2"
@@ -3281,10 +3336,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@jridgewell/resolve-uri@npm:3.1.0, @jridgewell/resolve-uri@npm:^3.0.3":
-  version: 3.1.0
-  resolution: "@jridgewell/resolve-uri@npm:3.1.0"
-  checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267
+"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0":
+  version: 3.1.1
+  resolution: "@jridgewell/resolve-uri@npm:3.1.1"
+  checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653
   languageName: node
   linkType: hard
 
@@ -3305,10 +3360,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10":
-  version: 1.4.14
-  resolution: "@jridgewell/sourcemap-codec@npm:1.4.14"
-  checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97
+"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14":
+  version: 1.4.15
+  resolution: "@jridgewell/sourcemap-codec@npm:1.4.15"
+  checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8
   languageName: node
   linkType: hard
 
@@ -3322,13 +3377,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@jridgewell/trace-mapping@npm:^0.3.0, @jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.7, @jridgewell/trace-mapping@npm:^0.3.9":
-  version: 0.3.17
-  resolution: "@jridgewell/trace-mapping@npm:0.3.17"
+"@jridgewell/trace-mapping@npm:^0.3.0, @jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.7, @jridgewell/trace-mapping@npm:^0.3.9":
+  version: 0.3.19
+  resolution: "@jridgewell/trace-mapping@npm:0.3.19"
   dependencies:
-    "@jridgewell/resolve-uri": 3.1.0
-    "@jridgewell/sourcemap-codec": 1.4.14
-  checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339
+    "@jridgewell/resolve-uri": ^3.1.0
+    "@jridgewell/sourcemap-codec": ^1.4.14
+  checksum: 956a6f0f6fec060fb48c6bf1f5ec2064e13cd38c8be3873877d4b92b4a27ba58289a34071752671262a3e3c202abcc3fa2aac64d8447b4b0fa1ba3c9047f1c20
   languageName: node
   linkType: hard
 
@@ -4083,7 +4138,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.10, @pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3":
+"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.10, @pmmmwh/react-refresh-webpack-plugin@npm:^0.5.10, @pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3":
   version: 0.5.10
   resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.10"
   dependencies:
@@ -4246,6 +4301,185 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@rspack/binding-darwin-arm64@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-darwin-arm64@npm:0.3.6"
+  conditions: os=darwin & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-darwin-x64@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-darwin-x64@npm:0.3.6"
+  conditions: os=darwin & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-linux-arm64-gnu@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-linux-arm64-gnu@npm:0.3.6"
+  conditions: os=linux & cpu=arm64 & libc=glibc
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-linux-arm64-musl@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-linux-arm64-musl@npm:0.3.6"
+  conditions: os=linux & cpu=arm64 & libc=musl
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-linux-x64-gnu@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-linux-x64-gnu@npm:0.3.6"
+  conditions: os=linux & cpu=x64 & libc=glibc
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-linux-x64-musl@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-linux-x64-musl@npm:0.3.6"
+  conditions: os=linux & cpu=x64 & libc=musl
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-win32-arm64-msvc@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-win32-arm64-msvc@npm:0.3.6"
+  conditions: os=win32 & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-win32-ia32-msvc@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-win32-ia32-msvc@npm:0.3.6"
+  conditions: os=win32 & cpu=ia32
+  languageName: node
+  linkType: hard
+
+"@rspack/binding-win32-x64-msvc@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding-win32-x64-msvc@npm:0.3.6"
+  conditions: os=win32 & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@rspack/binding@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/binding@npm:0.3.6"
+  dependencies:
+    "@rspack/binding-darwin-arm64": 0.3.6
+    "@rspack/binding-darwin-x64": 0.3.6
+    "@rspack/binding-linux-arm64-gnu": 0.3.6
+    "@rspack/binding-linux-arm64-musl": 0.3.6
+    "@rspack/binding-linux-x64-gnu": 0.3.6
+    "@rspack/binding-linux-x64-musl": 0.3.6
+    "@rspack/binding-win32-arm64-msvc": 0.3.6
+    "@rspack/binding-win32-ia32-msvc": 0.3.6
+    "@rspack/binding-win32-x64-msvc": 0.3.6
+  dependenciesMeta:
+    "@rspack/binding-darwin-arm64":
+      optional: true
+    "@rspack/binding-darwin-x64":
+      optional: true
+    "@rspack/binding-linux-arm64-gnu":
+      optional: true
+    "@rspack/binding-linux-arm64-musl":
+      optional: true
+    "@rspack/binding-linux-x64-gnu":
+      optional: true
+    "@rspack/binding-linux-x64-musl":
+      optional: true
+    "@rspack/binding-win32-arm64-msvc":
+      optional: true
+    "@rspack/binding-win32-ia32-msvc":
+      optional: true
+    "@rspack/binding-win32-x64-msvc":
+      optional: true
+  checksum: 0f7cda3c46b0846e3315ace975f01da40560b3cbc0fdee4f1211d5486d835a75eaaa76753173a615ed538c8dec6b47ef3e9c135edd7645bdd06a8491dd7baa47
+  languageName: node
+  linkType: hard
+
+"@rspack/cli@npm:^0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/cli@npm:0.3.6"
+  dependencies:
+    "@discoveryjs/json-ext": ^0.5.7
+    "@rspack/core": 0.3.6
+    "@rspack/dev-server": 0.3.6
+    colorette: 2.0.19
+    exit-hook: ^3.2.0
+    interpret: ^3.1.1
+    rechoir: ^0.8.0
+    semver: 6.3.1
+    webpack-bundle-analyzer: 4.6.1
+    yargs: 17.6.2
+  bin:
+    rspack: bin/rspack
+  checksum: e4abda14b91dd6f1e8e8c75f57292d5d56450bd2836d8fb92c1faf65529d2cc1bac1ed518bcffc8e189aa8eec12ba3ffe306ff8ec5e224b469acfe391f35d2fc
+  languageName: node
+  linkType: hard
+
+"@rspack/core@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/core@npm:0.3.6"
+  dependencies:
+    "@rspack/binding": 0.3.6
+    "@swc/helpers": 0.5.1
+    browserslist: ^4.21.3
+    compare-versions: 6.0.0-rc.1
+    enhanced-resolve: 5.12.0
+    graceful-fs: 4.2.10
+    neo-async: 2.6.2
+    react-refresh: 0.14.0
+    schema-utils: ^4.0.0
+    tapable: 2.2.1
+    terminal-link: ^2.1.1
+    util: 0.12.5
+    watchpack: ^2.4.0
+    webpack-sources: 3.2.3
+    zod: ^3.21.4
+    zod-validation-error: 1.2.0
+  checksum: 0888ce92fb6fe8b3df948ab5730321e711ce7f63d8e32479798399aad0e3935b8680885018a3d8e5d467f31d1713b8fa0b42a6f0d8b9c3ba116083dd11f62eb4
+  languageName: node
+  linkType: hard
+
+"@rspack/dev-server@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/dev-server@npm:0.3.6"
+  dependencies:
+    "@rspack/dev-server": 0.3.6
+    "@rspack/plugin-react-refresh": 0.3.6
+    chokidar: 3.5.3
+    connect-history-api-fallback: 2.0.0
+    express: 4.18.1
+    http-proxy-middleware: 2.0.6
+    mime-types: 2.1.35
+    webpack: 5.76.0
+    webpack-dev-middleware: 6.0.2
+    webpack-dev-server: 4.13.1
+    ws: 8.8.1
+  peerDependencies:
+    "@rspack/core": "*"
+  checksum: 19c70b3c94e08ba95c0962f398a074a13397c0be557726e5fb5bb793d272b253fae51aecbe6504a17cc7f10ebf05d1c79268a8458fb6c88eac95544ca976c07b
+  languageName: node
+  linkType: hard
+
+"@rspack/plugin-react-refresh@npm:0.3.6":
+  version: 0.3.6
+  resolution: "@rspack/plugin-react-refresh@npm:0.3.6"
+  dependencies:
+    "@pmmmwh/react-refresh-webpack-plugin": 0.5.10
+    schema-utils: ^4.0.0
+  peerDependencies:
+    react-refresh: ">=0.10.0 <1.0.0"
+  peerDependenciesMeta:
+    react-refresh:
+      optional: true
+  checksum: 3d29a54f3ae3feb19f08ac79d4f84483b0c00eeee749d11f503b7564f70b3cc90e8f68095ee38ae0718d520d84c8dc7936e9047e27a202867947eded337f40e1
+  languageName: node
+  linkType: hard
+
 "@sideway/address@npm:^4.1.3":
   version: 4.1.4
   resolution: "@sideway/address@npm:4.1.4"
@@ -4276,6 +4510,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@sinclair/typebox@npm:^0.27.8":
+  version: 0.27.8
+  resolution: "@sinclair/typebox@npm:0.27.8"
+  checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1
+  languageName: node
+  linkType: hard
+
 "@sindresorhus/is@npm:^0.14.0":
   version: 0.14.0
   resolution: "@sindresorhus/is@npm:0.14.0"
@@ -5776,6 +6017,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@swc/helpers@npm:0.5.1":
+  version: 0.5.1
+  resolution: "@swc/helpers@npm:0.5.1"
+  dependencies:
+    tslib: ^2.4.0
+  checksum: 71e0e27234590435e4c62b97ef5e796f88e786841a38c7116a5e27a3eafa7b9ead7cdec5249b32165902076de78446945311c973e59bddf77c1e24f33a8f272a
+  languageName: node
+  linkType: hard
+
 "@swc/helpers@npm:^0.4.11":
   version: 0.4.14
   resolution: "@swc/helpers@npm:0.4.14"
@@ -9462,7 +9712,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.1, chokidar@npm:^3.4.2, chokidar@npm:^3.5.1, chokidar@npm:^3.5.3":
+"chokidar@npm:3.5.3, chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.1, chokidar@npm:^3.4.2, chokidar@npm:^3.5.1, chokidar@npm:^3.5.3":
   version: 3.5.3
   resolution: "chokidar@npm:3.5.3"
   dependencies:
@@ -9812,6 +10062,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"colorette@npm:2.0.19, colorette@npm:^2.0.10":
+  version: 2.0.19
+  resolution: "colorette@npm:2.0.19"
+  checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427
+  languageName: node
+  linkType: hard
+
 "colorette@npm:^1.1.0, colorette@npm:^1.2.2":
   version: 1.4.0
   resolution: "colorette@npm:1.4.0"
@@ -9819,13 +10076,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"colorette@npm:^2.0.10":
-  version: 2.0.19
-  resolution: "colorette@npm:2.0.19"
-  checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427
-  languageName: node
-  linkType: hard
-
 "combine-promises@npm:^1.1.0":
   version: 1.1.0
   resolution: "combine-promises@npm:1.1.0"
@@ -9912,6 +10162,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"compare-versions@npm:6.0.0-rc.1":
+  version: 6.0.0-rc.1
+  resolution: "compare-versions@npm:6.0.0-rc.1"
+  checksum: ada8d19dab267ef432e7aea91ff7b4422284e3b44245a4d7b4caaf1353d0279e5f17b3767050e06a9cf84670120d19b427b5191b71b7284a125482fcd1eda789
+  languageName: node
+  linkType: hard
+
 "component-emitter@npm:^1.2.1":
   version: 1.3.0
   resolution: "component-emitter@npm:1.3.0"
@@ -10014,7 +10271,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"connect-history-api-fallback@npm:^2.0.0":
+"connect-history-api-fallback@npm:2.0.0, connect-history-api-fallback@npm:^2.0.0":
   version: 2.0.0
   resolution: "connect-history-api-fallback@npm:2.0.0"
   checksum: dc5368690f4a5c413889792f8df70d5941ca9da44523cde3f87af0745faee5ee16afb8195434550f0504726642734f2683d6c07f8b460f828a12c45fbd4c9a68
@@ -10081,6 +10338,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"convert-source-map@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "convert-source-map@npm:2.0.0"
+  checksum: 63ae9933be5a2b8d4509daca5124e20c14d023c820258e484e32dc324d34c2754e71297c94a05784064ad27615037ef677e3f0c00469fb55f409d2bb21261035
+  languageName: node
+  linkType: hard
+
 "cookie-signature@npm:1.0.6":
   version: 1.0.6
   resolution: "cookie-signature@npm:1.0.6"
@@ -11521,6 +11785,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"diff-sequences@npm:^29.6.3":
+  version: 29.6.3
+  resolution: "diff-sequences@npm:29.6.3"
+  checksum: f4914158e1f2276343d98ff5b31fc004e7304f5470bf0f1adb2ac6955d85a531a6458d33e87667f98f6ae52ebd3891bb47d420bb48a5bd8b7a27ee25b20e33aa
+  languageName: node
+  linkType: hard
+
 "diff@npm:^4.0.1":
   version: 4.0.2
   resolution: "diff@npm:4.0.2"
@@ -11934,6 +12205,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"enhanced-resolve@npm:5.12.0, enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.7.0, enhanced-resolve@npm:^5.8.2":
+  version: 5.12.0
+  resolution: "enhanced-resolve@npm:5.12.0"
+  dependencies:
+    graceful-fs: ^4.2.4
+    tapable: ^2.2.0
+  checksum: bf3f787facaf4ce3439bef59d148646344e372bef5557f0d37ea8aa02c51f50a925cd1f07b8d338f18992c29f544ec235a8c64bcdb56030196c48832a5494174
+  languageName: node
+  linkType: hard
+
 "enhanced-resolve@npm:^4.5.0":
   version: 4.5.0
   resolution: "enhanced-resolve@npm:4.5.0"
@@ -11945,16 +12226,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.7.0, enhanced-resolve@npm:^5.8.2":
-  version: 5.12.0
-  resolution: "enhanced-resolve@npm:5.12.0"
-  dependencies:
-    graceful-fs: ^4.2.4
-    tapable: ^2.2.0
-  checksum: bf3f787facaf4ce3439bef59d148646344e372bef5557f0d37ea8aa02c51f50a925cd1f07b8d338f18992c29f544ec235a8c64bcdb56030196c48832a5494174
-  languageName: node
-  linkType: hard
-
 "enquirer@npm:~2.3.6":
   version: 2.3.6
   resolution: "enquirer@npm:2.3.6"
@@ -12914,6 +13185,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"exit-hook@npm:^3.2.0":
+  version: 3.2.0
+  resolution: "exit-hook@npm:3.2.0"
+  checksum: 48b7ab6538043eb5b91379852fa01cb301810806f181f2f784fc596a6ce08016bb8b636881b873a3cf0a604f3ec746a6da52081a9c2e02b1858c59481cf00442
+  languageName: node
+  linkType: hard
+
 "exit@npm:^0.1.2":
   version: 0.1.2
   resolution: "exit@npm:0.1.2"
@@ -12956,7 +13234,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"express@npm:^4.17.1, express@npm:^4.17.3":
+"expect@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "expect@npm:29.7.0"
+  dependencies:
+    "@jest/expect-utils": ^29.7.0
+    jest-get-type: ^29.6.3
+    jest-matcher-utils: ^29.7.0
+    jest-message-util: ^29.7.0
+    jest-util: ^29.7.0
+  checksum: 9257f10288e149b81254a0fda8ffe8d54a7061cd61d7515779998b012579d2b8c22354b0eb901daf0145f347403da582f75f359f4810c007182ad3fb318b5c0c
+  languageName: node
+  linkType: hard
+
+"express@npm:4.18.1, express@npm:^4.17.1, express@npm:^4.17.3":
   version: 4.18.1
   resolution: "express@npm:4.18.1"
   dependencies:
@@ -13100,7 +13391,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0":
+"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0":
   version: 2.1.0
   resolution: "fast-json-stable-stringify@npm:2.1.0"
   checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb
@@ -13826,10 +14117,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fs-monkey@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "fs-monkey@npm:1.0.3"
-  checksum: cf50804833f9b88a476911ae911fe50f61a98d986df52f890bd97e7262796d023698cb2309fa9b74fdd8974f04315b648748a0a8ee059e7d5257b293bfc409c0
+"fs-monkey@npm:^1.0.3, fs-monkey@npm:^1.0.4":
+  version: 1.0.5
+  resolution: "fs-monkey@npm:1.0.5"
+  checksum: 424b67f65b37fe66117ae2bb061f790fe6d4b609e1d160487c74b3d69fbf42f262c665ccfba32e8b5f113f96f92e9a58fcdebe42d5f6649bdfc72918093a3119
   languageName: node
   linkType: hard
 
@@ -14348,7 +14639,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
+"graceful-fs@npm:4.2.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
   version: 4.2.10
   resolution: "graceful-fs@npm:4.2.10"
   checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da
@@ -14400,6 +14691,7 @@ __metadata:
     "@nrwl/web": 15.3.3
     "@nrwl/workspace": 15.3.3
     "@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
+    "@rspack/cli": ^0.3.6
     "@storybook/addon-essentials": 6.5.15
     "@storybook/builder-webpack5": 6.5.15
     "@storybook/manager-webpack5": 6.5.15
@@ -14485,6 +14777,7 @@ __metadata:
     react-test-renderer: 18.0.0
     rtl-css-js: ^1.16.1
     simple-git-hooks: 2.7.0
+    snapshot-diff: 0.10.0
     source-map-js: 1.0.2
     styled-components: ^5.3.6
     stylelint: ^15.10.3
@@ -15073,7 +15366,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"http-proxy-middleware@npm:^2.0.3":
+"http-proxy-middleware@npm:2.0.6, http-proxy-middleware@npm:^2.0.3":
   version: 2.0.6
   resolution: "http-proxy-middleware@npm:2.0.6"
   dependencies:
@@ -15492,6 +15785,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"interpret@npm:^3.1.1":
+  version: 3.1.1
+  resolution: "interpret@npm:3.1.1"
+  checksum: 35cebcf48c7351130437596d9ab8c8fe131ce4038da4561e6d665f25640e0034702a031cf7e3a5cea60ac7ac548bf17465e0571ede126f3d3a6933152171ac82
+  languageName: node
+  linkType: hard
+
 "intersection-observer@npm:^0.10.0":
   version: 0.10.0
   resolution: "intersection-observer@npm:0.10.0"
@@ -15578,7 +15878,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-arguments@npm:^1.1.0":
+"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.0":
   version: 1.1.1
   resolution: "is-arguments@npm:1.1.1"
   dependencies:
@@ -15826,6 +16126,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"is-generator-function@npm:^1.0.7":
+  version: 1.0.10
+  resolution: "is-generator-function@npm:1.0.10"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b
+  languageName: node
+  linkType: hard
+
 "is-glob@npm:^3.0.0, is-glob@npm:^3.1.0":
   version: 3.1.0
   resolution: "is-glob@npm:3.1.0"
@@ -16129,16 +16438,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9":
-  version: 1.1.10
-  resolution: "is-typed-array@npm:1.1.10"
+"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9":
+  version: 1.1.12
+  resolution: "is-typed-array@npm:1.1.12"
   dependencies:
-    available-typed-arrays: ^1.0.5
-    call-bind: ^1.0.2
-    for-each: ^0.3.3
-    gopd: ^1.0.1
-    has-tostringtag: ^1.0.0
-  checksum: aac6ecb59d4c56a1cdeb69b1f129154ef462bbffe434cb8a8235ca89b42f258b7ae94073c41b3cb7bce37f6a1733ad4499f07882d5d5093a7ba84dfc4ebb8017
+    which-typed-array: ^1.1.11
+  checksum: 4c89c4a3be07186caddadf92197b17fda663a9d259ea0d44a85f171558270d36059d1c386d34a12cba22dfade5aba497ce22778e866adc9406098c8fc4771796
   languageName: node
   linkType: hard
 
@@ -16551,6 +16856,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-diff@npm:^29.0.0, jest-diff@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-diff@npm:29.7.0"
+  dependencies:
+    chalk: ^4.0.0
+    diff-sequences: ^29.6.3
+    jest-get-type: ^29.6.3
+    pretty-format: ^29.7.0
+  checksum: 08e24a9dd43bfba1ef07a6374e5af138f53137b79ec3d5cc71a2303515335898888fa5409959172e1e05de966c9e714368d15e8994b0af7441f0721ee8e1bb77
+  languageName: node
+  linkType: hard
+
 "jest-docblock@npm:^28.1.1":
   version: 28.1.1
   resolution: "jest-docblock@npm:28.1.1"
@@ -16610,6 +16927,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-get-type@npm:^29.6.3":
+  version: 29.6.3
+  resolution: "jest-get-type@npm:29.6.3"
+  checksum: 88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205
+  languageName: node
+  linkType: hard
+
 "jest-haste-map@npm:^26.6.2":
   version: 26.6.2
   resolution: "jest-haste-map@npm:26.6.2"
@@ -16658,6 +16982,29 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-haste-map@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-haste-map@npm:29.7.0"
+  dependencies:
+    "@jest/types": ^29.6.3
+    "@types/graceful-fs": ^4.1.3
+    "@types/node": "*"
+    anymatch: ^3.0.3
+    fb-watchman: ^2.0.0
+    fsevents: ^2.3.2
+    graceful-fs: ^4.2.9
+    jest-regex-util: ^29.6.3
+    jest-util: ^29.7.0
+    jest-worker: ^29.7.0
+    micromatch: ^4.0.4
+    walker: ^1.0.8
+  dependenciesMeta:
+    fsevents:
+      optional: true
+  checksum: c2c8f2d3e792a963940fbdfa563ce14ef9e14d4d86da645b96d3cd346b8d35c5ce0b992ee08593939b5f718cf0a1f5a90011a056548a1dbf58397d4356786f01
+  languageName: node
+  linkType: hard
+
 "jest-leak-detector@npm:^28.1.3":
   version: 28.1.3
   resolution: "jest-leak-detector@npm:28.1.3"
@@ -16680,6 +17027,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-matcher-utils@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-matcher-utils@npm:29.7.0"
+  dependencies:
+    chalk: ^4.0.0
+    jest-diff: ^29.7.0
+    jest-get-type: ^29.6.3
+    pretty-format: ^29.7.0
+  checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd
+  languageName: node
+  linkType: hard
+
 "jest-message-util@npm:^28.1.1, jest-message-util@npm:^28.1.3":
   version: 28.1.3
   resolution: "jest-message-util@npm:28.1.3"
@@ -16697,6 +17056,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-message-util@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-message-util@npm:29.7.0"
+  dependencies:
+    "@babel/code-frame": ^7.12.13
+    "@jest/types": ^29.6.3
+    "@types/stack-utils": ^2.0.0
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.9
+    micromatch: ^4.0.4
+    pretty-format: ^29.7.0
+    slash: ^3.0.0
+    stack-utils: ^2.0.3
+  checksum: a9d025b1c6726a2ff17d54cc694de088b0489456c69106be6b615db7a51b7beb66788bea7a59991a019d924fbf20f67d085a445aedb9a4d6760363f4d7d09930
+  languageName: node
+  linkType: hard
+
 "jest-mock@npm:^28.1.1, jest-mock@npm:^28.1.3":
   version: 28.1.3
   resolution: "jest-mock@npm:28.1.3"
@@ -16733,6 +17109,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-regex-util@npm:^29.6.3":
+  version: 29.6.3
+  resolution: "jest-regex-util@npm:29.6.3"
+  checksum: 0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a
+  languageName: node
+  linkType: hard
+
 "jest-resolve-dependencies@npm:^28.1.3":
   version: 28.1.3
   resolution: "jest-resolve-dependencies@npm:28.1.3"
@@ -16877,6 +17260,34 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-snapshot@npm:^29.0.0":
+  version: 29.7.0
+  resolution: "jest-snapshot@npm:29.7.0"
+  dependencies:
+    "@babel/core": ^7.11.6
+    "@babel/generator": ^7.7.2
+    "@babel/plugin-syntax-jsx": ^7.7.2
+    "@babel/plugin-syntax-typescript": ^7.7.2
+    "@babel/types": ^7.3.3
+    "@jest/expect-utils": ^29.7.0
+    "@jest/transform": ^29.7.0
+    "@jest/types": ^29.6.3
+    babel-preset-current-node-syntax: ^1.0.0
+    chalk: ^4.0.0
+    expect: ^29.7.0
+    graceful-fs: ^4.2.9
+    jest-diff: ^29.7.0
+    jest-get-type: ^29.6.3
+    jest-matcher-utils: ^29.7.0
+    jest-message-util: ^29.7.0
+    jest-util: ^29.7.0
+    natural-compare: ^1.4.0
+    pretty-format: ^29.7.0
+    semver: ^7.5.3
+  checksum: 86821c3ad0b6899521ce75ee1ae7b01b17e6dfeff9166f2cf17f012e0c5d8c798f30f9e4f8f7f5bed01ea7b55a6bc159f5eda778311162cbfa48785447c237ad
+  languageName: node
+  linkType: hard
+
 "jest-util@npm:28.1.1":
   version: 28.1.1
   resolution: "jest-util@npm:28.1.1"
@@ -16919,6 +17330,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-util@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-util@npm:29.7.0"
+  dependencies:
+    "@jest/types": ^29.6.3
+    "@types/node": "*"
+    chalk: ^4.0.0
+    ci-info: ^3.2.0
+    graceful-fs: ^4.2.9
+    picomatch: ^2.2.3
+  checksum: 042ab4980f4ccd4d50226e01e5c7376a8556b472442ca6091a8f102488c0f22e6e8b89ea874111d2328a2080083bf3225c86f3788c52af0bd0345a00eb57a3ca
+  languageName: node
+  linkType: hard
+
 "jest-validate@npm:^28.1.1, jest-validate@npm:^28.1.3":
   version: 28.1.3
   resolution: "jest-validate@npm:28.1.3"
@@ -16982,6 +17407,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-worker@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "jest-worker@npm:29.7.0"
+  dependencies:
+    "@types/node": "*"
+    jest-util: ^29.7.0
+    merge-stream: ^2.0.0
+    supports-color: ^8.0.0
+  checksum: 30fff60af49675273644d408b650fc2eb4b5dcafc5a0a455f238322a8f9d8a98d847baca9d51ff197b6747f54c7901daa2287799230b856a0f48287d131f8c13
+  languageName: node
+  linkType: hard
+
 "jest@npm:28.1.3":
   version: 28.1.3
   resolution: "jest@npm:28.1.3"
@@ -17361,6 +17798,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"launch-editor@npm:^2.6.0":
+  version: 2.6.1
+  resolution: "launch-editor@npm:2.6.1"
+  dependencies:
+    picocolors: ^1.0.0
+    shell-quote: ^1.8.1
+  checksum: e06d193075ac09f7f8109f10cabe464a211bf7ed4cbe75f83348d6f67bf4d9f162f06e7a1ab3e1cd7fc250b5342c3b57080618aff2e646dc34248fe499227601
+  languageName: node
+  linkType: hard
+
 "lazy-universal-dotenv@npm:^3.0.1":
   version: 3.0.1
   resolution: "lazy-universal-dotenv@npm:3.0.1"
@@ -18156,12 +18603,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"memfs@npm:^3.1.2, memfs@npm:^3.2.2, memfs@npm:^3.4.1, memfs@npm:^3.4.3":
-  version: 3.4.13
-  resolution: "memfs@npm:3.4.13"
+"memfs@npm:^3.1.2, memfs@npm:^3.2.2, memfs@npm:^3.4.1, memfs@npm:^3.4.12, memfs@npm:^3.4.3":
+  version: 3.5.3
+  resolution: "memfs@npm:3.5.3"
   dependencies:
-    fs-monkey: ^1.0.3
-  checksum: 3f9717d6f060919d53f211acb6096a0ea2f566a8cbcc4ef7e1f2561e31e33dc456053fdf951c90a49c8ec55402de7f01b006b81683ab7bd4bdbbd8c9b9cdae5f
+    fs-monkey: ^1.0.4
+  checksum: 18dfdeacad7c8047b976a6ccd58bc98ba76e122ad3ca0e50a21837fe2075fc0d9aafc58ab9cf2576c2b6889da1dd2503083f2364191b695273f40969db2ecc44
   languageName: node
   linkType: hard
 
@@ -18436,7 +18883,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.30, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
+"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.30, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
   version: 2.1.35
   resolution: "mime-types@npm:2.1.35"
   dependencies:
@@ -18929,7 +19376,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"neo-async@npm:^2.5.0, neo-async@npm:^2.6.0, neo-async@npm:^2.6.1, neo-async@npm:^2.6.2":
+"neo-async@npm:2.6.2, neo-async@npm:^2.5.0, neo-async@npm:^2.6.0, neo-async@npm:^2.6.1, neo-async@npm:^2.6.2":
   version: 2.6.2
   resolution: "neo-async@npm:2.6.2"
   checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9
@@ -21075,6 +21522,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0":
+  version: 29.7.0
+  resolution: "pretty-format@npm:29.7.0"
+  dependencies:
+    "@jest/schemas": ^29.6.3
+    ansi-styles: ^5.0.0
+    react-is: ^18.0.0
+  checksum: 032c1602383e71e9c0c02a01bbd25d6759d60e9c7cf21937dde8357aa753da348fcec5def5d1002c9678a8524d5fe099ad98861286550ef44de8808cc61e43b6
+  languageName: node
+  linkType: hard
+
 "pretty-hrtime@npm:^1.0.3":
   version: 1.0.3
   resolution: "pretty-hrtime@npm:1.0.3"
@@ -21717,6 +22175,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-refresh@npm:0.14.0":
+  version: 0.14.0
+  resolution: "react-refresh@npm:0.14.0"
+  checksum: dc69fa8c993df512f42dd0f1b604978ae89bd747c0ed5ec595c0cc50d535fb2696619ccd98ae28775cc01d0a7c146a532f0f7fb81dc22e1977c242a4912312f4
+  languageName: node
+  linkType: hard
+
 "react-refresh@npm:^0.10.0":
   version: 0.10.0
   resolution: "react-refresh@npm:0.10.0"
@@ -21997,6 +22462,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"rechoir@npm:^0.8.0":
+  version: 0.8.0
+  resolution: "rechoir@npm:0.8.0"
+  dependencies:
+    resolve: ^1.20.0
+  checksum: ad3caed8afdefbc33fbc30e6d22b86c35b3d51c2005546f4e79bcc03c074df804b3640ad18945e6bef9ed12caedc035655ec1082f64a5e94c849ff939dc0a788
+  languageName: node
+  linkType: hard
+
 "recursive-readdir@npm:^2.2.2":
   version: 2.2.3
   resolution: "recursive-readdir@npm:2.2.3"
@@ -22967,12 +23441,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"selfsigned@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "selfsigned@npm:2.0.1"
+"selfsigned@npm:^2.1.1":
+  version: 2.1.1
+  resolution: "selfsigned@npm:2.1.1"
   dependencies:
     node-forge: ^1
-  checksum: 864e65c2f31ca877bce3ccdaa3bdef5e1e992b63b2a03641e00c24cd305bf2acce093431d1fed2e5ae9f526558db4be5e90baa2b3474c0428fcf7e25cc86ac93
+  checksum: aa9ce2150a54838978d5c0aee54d7ebe77649a32e4e690eb91775f71fdff773874a4fbafd0ac73d8ec3b702ff8a395c604df4f8e8868528f36fd6c15076fb43a
   languageName: node
   linkType: hard
 
@@ -22994,6 +23468,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"semver@npm:6.3.1, semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0":
+  version: 6.3.1
+  resolution: "semver@npm:6.3.1"
+  bin:
+    semver: bin/semver.js
+  checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2
+  languageName: node
+  linkType: hard
+
 "semver@npm:7.3.4":
   version: 7.3.4
   resolution: "semver@npm:7.3.4"
@@ -23005,7 +23488,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"semver@npm:7.3.8, semver@npm:7.x, semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7":
+"semver@npm:7.3.8":
   version: 7.3.8
   resolution: "semver@npm:7.3.8"
   dependencies:
@@ -23016,12 +23499,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0":
-  version: 6.3.0
-  resolution: "semver@npm:6.3.0"
+"semver@npm:7.x, semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3":
+  version: 7.5.4
+  resolution: "semver@npm:7.5.4"
+  dependencies:
+    lru-cache: ^6.0.0
   bin:
-    semver: ./bin/semver.js
-  checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9
+    semver: bin/semver.js
+  checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3
   languageName: node
   linkType: hard
 
@@ -23236,10 +23721,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"shell-quote@npm:^1.7.3":
-  version: 1.7.3
-  resolution: "shell-quote@npm:1.7.3"
-  checksum: aca58e73a3a5d933d02e0bdddedc53ee14f7c2ec264f97ac915b9d4482d077a38e422aa664631d60a672cd3cdb4054eb2e6c0303f54882453dacb6483e482d34
+"shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1":
+  version: 1.8.1
+  resolution: "shell-quote@npm:1.8.1"
+  checksum: 5f01201f4ef504d4c6a9d0d283fa17075f6770bfbe4c5850b074974c68062f37929ca61700d95ad2ac8822e14e8c4b990ca0e6e9272e64befd74ce5e19f0736b
   languageName: node
   linkType: hard
 
@@ -23404,6 +23889,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"snapshot-diff@npm:0.10.0":
+  version: 0.10.0
+  resolution: "snapshot-diff@npm:0.10.0"
+  dependencies:
+    jest-diff: ^29.0.0
+    jest-snapshot: ^29.0.0
+    pretty-format: ^29.0.0
+  peerDependencies:
+    jest: ">=16"
+  checksum: d13e310faeed3f091265cc141ac8f19f192b2c6837e1aaabf6ea601ed8edc40471324609434ab85cb5220fff4c7103fa48c4f9f63c3a207944c55224c0dbde0b
+  languageName: node
+  linkType: hard
+
 "sockjs@npm:^0.3.24":
   version: 0.3.24
   resolution: "sockjs@npm:0.3.24"
@@ -24418,6 +24916,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tapable@npm:2.2.1, tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1":
+  version: 2.2.1
+  resolution: "tapable@npm:2.2.1"
+  checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51
+  languageName: node
+  linkType: hard
+
 "tapable@npm:^1.0.0, tapable@npm:^1.1.3":
   version: 1.1.3
   resolution: "tapable@npm:1.1.3"
@@ -24425,13 +24930,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1":
-  version: 2.2.1
-  resolution: "tapable@npm:2.2.1"
-  checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51
-  languageName: node
-  linkType: hard
-
 "tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0":
   version: 2.2.0
   resolution: "tar-stream@npm:2.2.0"
@@ -24475,7 +24973,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"terminal-link@npm:^2.0.0":
+"terminal-link@npm:^2.0.0, terminal-link@npm:^2.1.1":
   version: 2.1.1
   resolution: "terminal-link@npm:2.1.1"
   dependencies:
@@ -25677,6 +26175,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"util@npm:0.12.5":
+  version: 0.12.5
+  resolution: "util@npm:0.12.5"
+  dependencies:
+    inherits: ^2.0.3
+    is-arguments: ^1.0.4
+    is-generator-function: ^1.0.7
+    is-typed-array: ^1.1.3
+    which-typed-array: ^1.1.2
+  checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a
+  languageName: node
+  linkType: hard
+
 "util@npm:^0.11.0":
   version: 0.11.1
   resolution: "util@npm:0.11.1"
@@ -25952,9 +26463,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"webpack-bundle-analyzer@npm:^4.5.0":
-  version: 4.5.0
-  resolution: "webpack-bundle-analyzer@npm:4.5.0"
+"webpack-bundle-analyzer@npm:4.6.1, webpack-bundle-analyzer@npm:^4.5.0":
+  version: 4.6.1
+  resolution: "webpack-bundle-analyzer@npm:4.6.1"
   dependencies:
     acorn: ^8.0.4
     acorn-walk: ^8.0.0
@@ -25967,7 +26478,25 @@ __metadata:
     ws: ^7.3.1
   bin:
     webpack-bundle-analyzer: lib/bin/analyzer.js
-  checksum: 158e96810ec213d5665ca1c0b257097db44e1f11c4befefab8352b9e5b10890fcb3e3fc1f7bb400dd58762a8edce5621c92afeca86eb4687d2eb64e93186bfcb
+  checksum: 4bc97ac6a1d9cd1f133444b0fc9d9091c97f4bd8388f97636ce27abd1ebffaa7dd45d29f6693661a666e77bcc08dff43ab7c2f5e2600a3101b956c94c1d038d0
+  languageName: node
+  linkType: hard
+
+"webpack-dev-middleware@npm:6.0.2":
+  version: 6.0.2
+  resolution: "webpack-dev-middleware@npm:6.0.2"
+  dependencies:
+    colorette: ^2.0.10
+    memfs: ^3.4.12
+    mime-types: ^2.1.31
+    range-parser: ^1.2.1
+    schema-utils: ^4.0.0
+  peerDependencies:
+    webpack: ^5.0.0
+  peerDependenciesMeta:
+    webpack:
+      optional: true
+  checksum: a2f65ffdec8120ae2a2def17f0f2941e65b26ee2922708aaec47ada4e262ad99f4e644136d0463fa9bbc32c4168425a1f55d928b7f3e0a2a3905c579358141b8
   languageName: node
   linkType: hard
 
@@ -26017,9 +26546,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"webpack-dev-server@npm:^4.9.3":
-  version: 4.9.3
-  resolution: "webpack-dev-server@npm:4.9.3"
+"webpack-dev-server@npm:4.13.1, webpack-dev-server@npm:^4.9.3":
+  version: 4.13.1
+  resolution: "webpack-dev-server@npm:4.13.1"
   dependencies:
     "@types/bonjour": ^3.5.9
     "@types/connect-history-api-fallback": ^1.3.5
@@ -26040,24 +26569,27 @@ __metadata:
     html-entities: ^2.3.2
     http-proxy-middleware: ^2.0.3
     ipaddr.js: ^2.0.1
+    launch-editor: ^2.6.0
     open: ^8.0.9
     p-retry: ^4.5.0
     rimraf: ^3.0.2
     schema-utils: ^4.0.0
-    selfsigned: ^2.0.1
+    selfsigned: ^2.1.1
     serve-index: ^1.9.1
     sockjs: ^0.3.24
     spdy: ^4.0.2
     webpack-dev-middleware: ^5.3.1
-    ws: ^8.4.2
+    ws: ^8.13.0
   peerDependencies:
     webpack: ^4.37.0 || ^5.0.0
   peerDependenciesMeta:
+    webpack:
+      optional: true
     webpack-cli:
       optional: true
   bin:
     webpack-dev-server: bin/webpack-dev-server.js
-  checksum: 845f2cc8e79a348ee7b17080eef9b332c675540888e0bc97ec6b62174882aca7995eaa7a3f49cfdd9af186da22f2f335fd03cb3c55cd49e387c8a3dc59700d66
+  checksum: f70611544b7d964a31eb3d934d7c2b376b97e6927a89e03b2e21cfa5812bb639625cd18fd350de1604ba6c455b324135523a894032f28c69d90d90682e4f3b7d
   languageName: node
   linkType: hard
 
@@ -26109,6 +26641,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"webpack-sources@npm:3.2.3, webpack-sources@npm:^3.0.0, webpack-sources@npm:^3.2.2, webpack-sources@npm:^3.2.3":
+  version: 3.2.3
+  resolution: "webpack-sources@npm:3.2.3"
+  checksum: 989e401b9fe3536529e2a99dac8c1bdc50e3a0a2c8669cbafad31271eadd994bc9405f88a3039cd2e29db5e6d9d0926ceb7a1a4e7409ece021fe79c37d9c4607
+  languageName: node
+  linkType: hard
+
 "webpack-sources@npm:^1.4.0, webpack-sources@npm:^1.4.1, webpack-sources@npm:^1.4.3":
   version: 1.4.3
   resolution: "webpack-sources@npm:1.4.3"
@@ -26119,13 +26658,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"webpack-sources@npm:^3.0.0, webpack-sources@npm:^3.2.2, webpack-sources@npm:^3.2.3":
-  version: 3.2.3
-  resolution: "webpack-sources@npm:3.2.3"
-  checksum: 989e401b9fe3536529e2a99dac8c1bdc50e3a0a2c8669cbafad31271eadd994bc9405f88a3039cd2e29db5e6d9d0926ceb7a1a4e7409ece021fe79c37d9c4607
-  languageName: node
-  linkType: hard
-
 "webpack-subresource-integrity@npm:^5.1.0":
   version: 5.1.0
   resolution: "webpack-subresource-integrity@npm:5.1.0"
@@ -26195,6 +26727,43 @@ __metadata:
   languageName: node
   linkType: hard
 
+"webpack@npm:5.76.0":
+  version: 5.76.0
+  resolution: "webpack@npm:5.76.0"
+  dependencies:
+    "@types/eslint-scope": ^3.7.3
+    "@types/estree": ^0.0.51
+    "@webassemblyjs/ast": 1.11.1
+    "@webassemblyjs/wasm-edit": 1.11.1
+    "@webassemblyjs/wasm-parser": 1.11.1
+    acorn: ^8.7.1
+    acorn-import-assertions: ^1.7.6
+    browserslist: ^4.14.5
+    chrome-trace-event: ^1.0.2
+    enhanced-resolve: ^5.10.0
+    es-module-lexer: ^0.9.0
+    eslint-scope: 5.1.1
+    events: ^3.2.0
+    glob-to-regexp: ^0.4.1
+    graceful-fs: ^4.2.9
+    json-parse-even-better-errors: ^2.3.1
+    loader-runner: ^4.2.0
+    mime-types: ^2.1.27
+    neo-async: ^2.6.2
+    schema-utils: ^3.1.0
+    tapable: ^2.1.1
+    terser-webpack-plugin: ^5.1.3
+    watchpack: ^2.4.0
+    webpack-sources: ^3.2.3
+  peerDependenciesMeta:
+    webpack-cli:
+      optional: true
+  bin:
+    webpack: bin/webpack.js
+  checksum: e897b3b34068222fe910d8894837cbf39f5e66b4772fe8d24dfc0090cd13e19337a3a757a97bc185be6c64d9d5a7b1ae372821b65a6079ce8c7ceba606edb409
+  languageName: node
+  linkType: hard
+
 "webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^4 || ^5, webpack@npm:^5.73.0, webpack@npm:^5.75.0, webpack@npm:^5.76.0, webpack@npm:^5.9.0":
   version: 5.76.1
   resolution: "webpack@npm:5.76.1"
@@ -26330,17 +26899,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"which-typed-array@npm:^1.1.9":
-  version: 1.1.9
-  resolution: "which-typed-array@npm:1.1.9"
+"which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.9":
+  version: 1.1.11
+  resolution: "which-typed-array@npm:1.1.11"
   dependencies:
     available-typed-arrays: ^1.0.5
     call-bind: ^1.0.2
     for-each: ^0.3.3
     gopd: ^1.0.1
     has-tostringtag: ^1.0.0
-    is-typed-array: ^1.1.10
-  checksum: fe0178ca44c57699ca2c0e657b64eaa8d2db2372a4e2851184f568f98c478ae3dc3fdb5f7e46c384487046b0cf9e23241423242b277e03e8ba3dabc7c84c98ef
+  checksum: 711ffc8ef891ca6597b19539075ec3e08bb9b4c2ca1f78887e3c07a977ab91ac1421940505a197758fb5939aa9524976d0a5bbcac34d07ed6faa75cedbb17206
   languageName: node
   linkType: hard
 
@@ -26487,7 +27055,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"write-file-atomic@npm:^4.0.1":
+"write-file-atomic@npm:^4.0.1, write-file-atomic@npm:^4.0.2":
   version: 4.0.2
   resolution: "write-file-atomic@npm:4.0.2"
   dependencies:
@@ -26507,6 +27075,21 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ws@npm:8.8.1":
+  version: 8.8.1
+  resolution: "ws@npm:8.8.1"
+  peerDependencies:
+    bufferutil: ^4.0.1
+    utf-8-validate: ^5.0.2
+  peerDependenciesMeta:
+    bufferutil:
+      optional: true
+    utf-8-validate:
+      optional: true
+  checksum: 2152cf862cae0693f3775bc688a6afb2e989d19d626d215e70f5fcd8eb55b1c3b0d3a6a4052905ec320e2d7734e20aeedbf9744496d62f15a26ad79cf4cf7dae
+  languageName: node
+  linkType: hard
+
 "ws@npm:^7.3.1":
   version: 7.5.9
   resolution: "ws@npm:7.5.9"
@@ -26522,18 +27105,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ws@npm:^8.2.3, ws@npm:^8.4.2":
-  version: 8.8.1
-  resolution: "ws@npm:8.8.1"
+"ws@npm:^8.13.0, ws@npm:^8.2.3":
+  version: 8.14.2
+  resolution: "ws@npm:8.14.2"
   peerDependencies:
     bufferutil: ^4.0.1
-    utf-8-validate: ^5.0.2
+    utf-8-validate: ">=5.0.2"
   peerDependenciesMeta:
     bufferutil:
       optional: true
     utf-8-validate:
       optional: true
-  checksum: 2152cf862cae0693f3775bc688a6afb2e989d19d626d215e70f5fcd8eb55b1c3b0d3a6a4052905ec320e2d7734e20aeedbf9744496d62f15a26ad79cf4cf7dae
+  checksum: 3ca0dad26e8cc6515ff392b622a1467430814c463b3368b0258e33696b1d4bed7510bc7030f7b72838b9fdeb8dbd8839cbf808367d6aae2e1d668ce741d4308b
   languageName: node
   linkType: hard
 
@@ -26646,33 +27229,33 @@ __metadata:
   languageName: node
   linkType: hard
 
-"yargs@npm:^16.2.0":
-  version: 16.2.0
-  resolution: "yargs@npm:16.2.0"
+"yargs@npm:17.6.2, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2":
+  version: 17.6.2
+  resolution: "yargs@npm:17.6.2"
   dependencies:
-    cliui: ^7.0.2
+    cliui: ^8.0.1
     escalade: ^3.1.1
     get-caller-file: ^2.0.5
     require-directory: ^2.1.1
-    string-width: ^4.2.0
+    string-width: ^4.2.3
     y18n: ^5.0.5
-    yargs-parser: ^20.2.2
-  checksum: b14afbb51e3251a204d81937c86a7e9d4bdbf9a2bcee38226c900d00f522969ab675703bee2a6f99f8e20103f608382936034e64d921b74df82b63c07c5e8f59
+    yargs-parser: ^21.1.1
+  checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643
   languageName: node
   linkType: hard
 
-"yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2":
-  version: 17.6.2
-  resolution: "yargs@npm:17.6.2"
+"yargs@npm:^16.2.0":
+  version: 16.2.0
+  resolution: "yargs@npm:16.2.0"
   dependencies:
-    cliui: ^8.0.1
+    cliui: ^7.0.2
     escalade: ^3.1.1
     get-caller-file: ^2.0.5
     require-directory: ^2.1.1
-    string-width: ^4.2.3
+    string-width: ^4.2.0
     y18n: ^5.0.5
-    yargs-parser: ^21.1.1
-  checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643
+    yargs-parser: ^20.2.2
+  checksum: b14afbb51e3251a204d81937c86a7e9d4bdbf9a2bcee38226c900d00f522969ab675703bee2a6f99f8e20103f608382936034e64d921b74df82b63c07c5e8f59
   languageName: node
   linkType: hard
 
@@ -26708,6 +27291,22 @@ __metadata:
   languageName: node
   linkType: hard
 
+"zod-validation-error@npm:1.2.0":
+  version: 1.2.0
+  resolution: "zod-validation-error@npm:1.2.0"
+  peerDependencies:
+    zod: ^3.18.0
+  checksum: 10a06b4f12ec3d5b400b0d376a2752b74dc15be03e1768a0d5e96740b70a57a330c23cfbb10d2b2b96239314e88b7f78f2616b8847a7dba5c3cbedc4fd93d7a0
+  languageName: node
+  linkType: hard
+
+"zod@npm:^3.21.4":
+  version: 3.22.4
+  resolution: "zod@npm:3.22.4"
+  checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f
+  languageName: node
+  linkType: hard
+
 "zwitch@npm:^1.0.0":
   version: 1.0.5
   resolution: "zwitch@npm:1.0.5"


Test patch:
diff --git a/e2e/nextjs/src/test.ts b/e2e/nextjs/src/test.ts
index 35e8587..6d87c36 100644
--- a/e2e/nextjs/src/test.ts
+++ b/e2e/nextjs/src/test.ts
@@ -1,12 +1,15 @@
-import { configureYarn, copyAssets, createTempDir, installPackages, packLocalPackage, sh } from '@griffel/e2e-utils';
+import {
+  compareSnapshots,
+  configureYarn,
+  copyAssets,
+  createTempDir,
+  installPackages,
+  packLocalPackage,
+  sh,
+} from '@griffel/e2e-utils';
 import * as fs from 'fs';
 import * as logSymbols from 'log-symbols';
 import * as path from 'path';
-import * as prettier from 'prettier';
-
-function formatCSS(css: string): string {
-  return prettier.format(css, { parser: 'css' }).trim();
-}
 
 async function performTest() {
   const rootDir = path.resolve(__dirname, '..', '..', '..');
@@ -70,15 +73,11 @@ async function performTest() {
       throw new Error(`There are CSS files (${cssFiles.length}) than expected in "${cssFilesPath}"`);
     }
 
-    const cssFile = path.resolve(cssFilesPath, cssFiles[0]);
-    const cssContent = formatCSS(await fs.promises.readFile(cssFile, 'utf8'));
-
-    const cssSnapshotPath = path.resolve(__dirname, 'snapshots', 'output.css');
-    const cssSnapshot = formatCSS(await fs.promises.readFile(cssSnapshotPath, 'utf8'));
-
-    if (cssContent !== cssSnapshot) {
-      throw new Error('CSS output does not match existing snapshot');
-    }
+    await compareSnapshots({
+      type: 'css',
+      snapshotFile: path.resolve(__dirname, 'snapshots', 'output.css'),
+      resultFile: path.resolve(cssFilesPath, cssFiles[0]),
+    });
 
     console.log(logSymbols.success, `Example project contains the same CSS as a snapshot`);
     console.log('');
diff --git a/e2e/rspack/.eslintrc.json b/e2e/rspack/.eslintrc.json
new file mode 100644
index 0000000..9d9c0db
--- /dev/null
+++ b/e2e/rspack/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+  "extends": ["../../.eslintrc.json"],
+  "ignorePatterns": ["!**/*"],
+  "overrides": [
+    {
+      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.ts", "*.tsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.js", "*.jsx"],
+      "rules": {}
+    }
+  ]
+}
diff --git a/e2e/rspack/README.md b/e2e/rspack/README.md
new file mode 100644
index 0000000..bdf1a2d
--- /dev/null
+++ b/e2e/rspack/README.md
@@ -0,0 +1,3 @@
+This test package was created to ensure that AOT & CSS extraction remains compatible with Rspack.
+
+The test uses `@griffel/webpack-loader` & `@griffel/webpack-extraction-plugin` and the recommended config from our docs.
diff --git a/e2e/rspack/project.json b/e2e/rspack/project.json
new file mode 100644
index 0000000..918e93e
--- /dev/null
+++ b/e2e/rspack/project.json
@@ -0,0 +1,27 @@
+{
+  "name": "@griffel/e2e-rspack",
+  "$schema": "../../node_modules/nx/schemas/project-schema.json",
+  "sourceRoot": "e2e/rspack/src",
+  "projectType": "library",
+  "implicitDependencies": ["@griffel/webpack-loader"],
+  "targets": {
+    "test": {
+      "executor": "nx:run-commands",
+      "dependsOn": [{ "target": "build", "projects": "dependencies" }],
+      "options": {
+        "cwd": "e2e/rspack",
+        "commands": [{ "command": "ts-node src/test.ts" }],
+        "outputPath": []
+      }
+    },
+    "type-check": {
+      "executor": "nx:run-commands",
+      "options": {
+        "cwd": "e2e/rspack",
+        "commands": [{ "command": "tsc -b --pretty" }],
+        "outputPath": []
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/e2e/rspack/src/assets/rspack.config.js b/e2e/rspack/src/assets/rspack.config.js
new file mode 100644
index 0000000..d776dd8
--- /dev/null
+++ b/e2e/rspack/src/assets/rspack.config.js
@@ -0,0 +1,35 @@
+// @ts-check
+
+const { GriffelCSSExtractionPlugin } = require('@griffel/webpack-extraction-plugin');
+const path = require('path');
+
+/**
+ * @type {import('@rspack/core').Configuration}
+ */
+const config = {
+  mode: 'production',
+  externals: {
+    '@griffel/react': 'Griffel',
+  },
+  optimization: {
+    minimize: false,
+  },
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        exclude: /node_modules/,
+        use: [{ loader: GriffelCSSExtractionPlugin.loader }, { loader: '@griffel/webpack-loader' }],
+      },
+    ],
+  },
+  plugins: [/** @type {any} */ (new GriffelCSSExtractionPlugin())],
+  resolve: {
+    alias: {
+      'fake-module': path.resolve(__dirname, 'src', 'Component.js'),
+      'fake-colors': path.resolve(__dirname, 'src', 'colors.js'),
+    },
+  },
+};
+
+module.exports = config;
diff --git a/e2e/rspack/src/assets/src/Component.js b/e2e/rspack/src/assets/src/Component.js
new file mode 100644
index 0000000..52ca050
--- /dev/null
+++ b/e2e/rspack/src/assets/src/Component.js
@@ -0,0 +1,34 @@
+import { makeResetStyles, makeStyles, shorthands } from '@griffel/react';
+// @ts-expect-error It's a fake module resolved via aliases
+import { colors } from 'fake-colors';
+
+const useClasses = makeStyles({
+  root: {
+    backgroundColor: colors.background,
+    color: colors.foreground,
+
+    ':focus': {
+      outlineOffset: '5px',
+    },
+
+    '@media (min-width: 968px) and (orientation: landscape)': {
+      width: '400px',
+    },
+  },
+  slot: {
+    ...shorthands.border('2px', 'dashed', 'magenta'),
+    ...shorthands.borderRadius('5px'),
+    ...shorthands.gap('5px'),
+    ...shorthands.padding('10px'),
+  },
+});
+
+const useBaseClass = makeResetStyles({
+  display: 'flex',
+  flexDirection: 'column',
+  width: '200px',
+});
+
+export function Component() {
+  return [useBaseClass(), useClasses()];
+}
diff --git a/e2e/rspack/src/assets/src/colors.js b/e2e/rspack/src/assets/src/colors.js
new file mode 100644
index 0000000..3ac6d03
--- /dev/null
+++ b/e2e/rspack/src/assets/src/colors.js
@@ -0,0 +1,4 @@
+export const colors = {
+  background: 'blue',
+  foreground: 'red',
+};
diff --git a/e2e/rspack/src/assets/src/index.js b/e2e/rspack/src/assets/src/index.js
new file mode 100644
index 0000000..63b7c27
--- /dev/null
+++ b/e2e/rspack/src/assets/src/index.js
@@ -0,0 +1,4 @@
+// @ts-expect-error It's a fake module resolved via aliases
+import { Component } from 'fake-module';
+
+console.log(Component);
diff --git a/e2e/rspack/src/snapshots/output.css b/e2e/rspack/src/snapshots/output.css
new file mode 100644
index 0000000..669953b
--- /dev/null
+++ b/e2e/rspack/src/snapshots/output.css
@@ -0,0 +1,85 @@
+.rddpni4 {
+  display: flex;
+  flex-direction: column;
+  width: 200px;
+}
+.f1bh81bl {
+  background-color: blue;
+}
+.fe3e8s9 {
+  color: red;
+}
+.ftac7j7 {
+  border-top-width: 2px;
+}
+.f1w0yd9v {
+  border-right-width: 2px;
+}
+.f1h0xgbp {
+  border-left-width: 2px;
+}
+.fdwcyh7 {
+  border-bottom-width: 2px;
+}
+.fntkm7k {
+  border-top-style: dashed;
+}
+.f1oq6gla {
+  border-right-style: dashed;
+}
+.flpbmyi {
+  border-left-style: dashed;
+}
+.fjbf411 {
+  border-bottom-style: dashed;
+}
+.f1vtfyy1 {
+  border-top-color: magenta;
+}
+.f1g7g05w {
+  border-right-color: magenta;
+}
+.f10ibvnv {
+  border-left-color: magenta;
+}
+.fa31z3g {
+  border-bottom-color: magenta;
+}
+.f19gwsd {
+  border-bottom-right-radius: 5px;
+}
+.f3xzbnz {
+  border-bottom-left-radius: 5px;
+}
+.f1cxotgb {
+  border-top-right-radius: 5px;
+}
+.fxymw9n {
+  border-top-left-radius: 5px;
+}
+.fl0tjuq {
+  column-gap: 5px;
+}
+.ffmv8ov {
+  row-gap: 5px;
+}
+.f1809wu7 {
+  padding-top: 10px;
+}
+.f81rol6 {
+  padding-right: 10px;
+}
+.frdkuqy {
+  padding-left: 10px;
+}
+.f1fow5ox {
+  padding-bottom: 10px;
+}
+.f1ir1d6m:focus {
+  outline-offset: 5px;
+}
+@media (min-width: 968px) and (orientation: landscape) {
+  .fnj14hp {
+    width: 400px;
+  }
+}
diff --git a/e2e/rspack/src/test.ts b/e2e/rspack/src/test.ts
new file mode 100644
index 0000000..6ac5dc9
--- /dev/null
+++ b/e2e/rspack/src/test.ts
@@ -0,0 +1,94 @@
+import {
+  compareSnapshots,
+  configureYarn,
+  copyAssets,
+  createTempDir,
+  installPackages,
+  packLocalPackage,
+  sh,
+} from '@griffel/e2e-utils';
+import * as fs from 'fs';
+import * as logSymbols from 'log-symbols';
+import * as path from 'path';
+
+async function performTest() {
+  const rootDir = path.resolve(__dirname, '..', '..', '..');
+
+  let tempDir: string;
+
+  try {
+    tempDir = createTempDir('rspack');
+
+    await copyAssets({ assetsPath: path.resolve(__dirname, 'assets'), tempDir });
+    await configureYarn({ tempDir, rootDir });
+
+    const resolutions = await Promise.all([
+      packLocalPackage(rootDir, tempDir, '@griffel/style-types'),
+      packLocalPackage(rootDir, tempDir, '@griffel/core'),
+      packLocalPackage(rootDir, tempDir, '@griffel/react'),
+      packLocalPackage(rootDir, tempDir, '@griffel/webpack-extraction-plugin'),
+      packLocalPackage(rootDir, tempDir, '@griffel/webpack-loader'),
+    ]);
+
+    const rspackVersion = (await sh(`yarn rspack --version`, rootDir, true)).trim();
+
+    console.log(logSymbols.info, 'Using Rspack', rspackVersion);
+    console.log(logSymbols.info, 'Installing packages...');
+
+    await installPackages({
+      packages: ['@rspack/cli', 'react', 'react-dom'],
+      resolutions,
+      tempDir,
+      rootDir,
+    });
+  } catch (e) {
+    console.error(logSymbols.error, 'Something went wrong setting up the test:');
+    console.error((e as Error)?.stack ?? e);
+    process.exit(1);
+  }
+
+  try {
+    await sh(`yarn rspack`, tempDir);
+
+    console.log(logSymbols.success, `Example project was successfully built with Rspack`);
+  } catch (e) {
+    console.error(e);
+
+    console.log('');
+    console.error(logSymbols.error, `Building a test project with Rspack failed.`);
+
+    process.exit(1);
+  }
+
+  try {
+    const distDir = path.resolve(tempDir, 'dist');
+    const distFiles = await fs.promises.readdir(distDir);
+
+    const cssFilename = distFiles.find(filename => filename.endsWith('griffel.css'));
+
+    if (!cssFilename) {
+      throw new Error(`Failed to find any matching CSS file in "${distDir}"`);
+    }
+
+    await compareSnapshots({
+      type: 'css',
+      snapshotFile: path.resolve(__dirname, 'snapshots', 'output.css'),
+      resultFile: path.resolve(distDir, cssFilename),
+    });
+
+    console.log(logSymbols.success, `Example project contains the same CSS as a snapshot`);
+    console.log('');
+    console.log('');
+  } catch (e) {
+    console.error(e);
+
+    console.log('');
+    console.error(logSymbols.error, `Validating CSS produced by Rspack build failed.`);
+
+    process.exit(1);
+  }
+}
+
+(async () => {
+  await performTest();
+})();
diff --git a/e2e/rspack/tsconfig.json b/e2e/rspack/tsconfig.json
new file mode 100644
index 0000000..fa31c0d
--- /dev/null
+++ b/e2e/rspack/tsconfig.json
@@ -0,0 +1,27 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "module": "CommonJS",
+    "jsx": "react",
+    "noEmit": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "files": [],
+  "include": [],
+  "references": [
+    {
+      "path": "./tsconfig.lib.json"
+    }
+  ],
+  "ts-node": {
+    "require": ["tsconfig-paths/register"],
+    "swc": true
+  }
+}
diff --git a/e2e/rspack/tsconfig.lib.json b/e2e/rspack/tsconfig.lib.json
new file mode 100644
index 0000000..cadf7b9
--- /dev/null
+++ b/e2e/rspack/tsconfig.lib.json
@@ -0,0 +1,8 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../dist/out-tsc",
+    "types": ["node", "environment"]
+  },
+  "include": ["**/*.ts", "**/*.tsx", "**/*.js"]
+}
diff --git a/e2e/utils/src/compareSnapshots.ts b/e2e/utils/src/compareSnapshots.ts
new file mode 100644
index 0000000..3289910
--- /dev/null
+++ b/e2e/utils/src/compareSnapshots.ts
@@ -0,0 +1,33 @@
+import * as fs from 'fs';
+import * as prettier from 'prettier';
+import * as snapshotDiff from 'snapshot-diff';
+
+type CompareSnapshotsOptions = {
+  type: 'css';
+  snapshotFile: string;
+  resultFile: string;
+};
+
+function formatCSS(css: string): string {
+  return prettier.format(css, { parser: 'css' }).trim();
+}
+
+export async function compareSnapshots(options: CompareSnapshotsOptions): Promise<void> {
+  const { snapshotFile, resultFile } = options;
+
+  const resultContent = formatCSS(await fs.promises.readFile(resultFile, 'utf8'));
+  const snapshotContent = formatCSS(await fs.promises.readFile(snapshotFile, 'utf8'));
+
+  const diff = snapshotDiff(snapshotContent, resultContent, {
+    colors: true,
+    aAnnotation: snapshotFile,
+    bAnnotation: resultFile,
+  });
+
+  if (diff.includes('Compared values have no visual difference.')) {
+    return;
+  }
+
+  console.log(diff);
+  throw new Error('CSS output does not match existing snapshot');
+}
diff --git a/e2e/utils/src/index.ts b/e2e/utils/src/index.ts
index 5eb898e..e27db95 100644
--- a/e2e/utils/src/index.ts
+++ b/e2e/utils/src/index.ts
@@ -1,3 +1,4 @@
+export { compareSnapshots } from './compareSnapshots';
 export { configureYarn } from './configureYarn';
 export { copyAssets } from './copyAssets';
 export { createTempDir } from './createTempDir';