Repository: microsoft/griffel
Pull request #279 (2022-11-23 15:40:56)

test: create E2E test for Next.js and CSS extraction
This PR adds E2E test for Next.js plugin to ensure that CSS extraction works as expected.

- Some utils were moved out from TS into `@griffel/e2e-utils` to share them between tests
- Tests and utils were rewritten to TS for better expirence
- `swc-node` was added to run TS code
- `log-symbols` was downgraded as it requires ESM, but `.mts` is not supported by SWC https://github.com/swc-project/swc/issues/4679

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

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

| Package & Exports                                                                                                            |        Size (minified/GZIP) |
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------: |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeResetStyles-runtime.fixture.js'>makeResetStyles (runtime)</abbr>       | `16.832 kB`<br />`6.245 kB` |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStyles.fixture.js'>makeStyles + mergeClasses (build time)</abbr>       |     `1.842 kB`<br />`877 B` |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStylesRuntime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr>   | `20.789 kB`<br />`7.724 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/__css.fixture.js'>\_\_css + mergeClasses (build time)</abbr>              |     `1.879 kB`<br />`874 B` |
| <samp>react</samp> <br /> <abbr title='bundle-size/__styles.fixture.js'>\_\_styles + mergeClasses (build time)</abbr>        |  `3.581 kB`<br />`1.614 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeResetStyles-runtime.fixture.js'>makeResetStyles (runtime)</abbr>      | `18.595 kB`<br />`6.981 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStaticStyles-runtime.fixture.js'>makeStaticStyles (runtime)</abbr>    |  `9.443 kB`<br />`4.068 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles-runtime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr> | `22.554 kB`<br />`8.436 kB` |

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

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/change/@griffel-core-da60ef94-dd69-4128-9f8f-53fceb3ec3c3.json b/change/@griffel-core-da60ef94-dd69-4128-9f8f-53fceb3ec3c3.json
deleted file mode 100644
index eb2ccb9..0000000
--- a/change/@griffel-core-da60ef94-dd69-4128-9f8f-53fceb3ec3c3.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "patch",
-  "comment": "chore: export \"CSSBucketEntry\" type for internal usage",
-  "packageName": "@griffel/core",
-  "email": "olfedias@microsoft.com",
-  "dependentChangeType": "patch"
-}
diff --git a/change/@griffel-react-3815ac0f-f221-4f2f-b4a6-0d1934fe6657.json b/change/@griffel-react-3815ac0f-f221-4f2f-b4a6-0d1934fe6657.json
new file mode 100644
index 0000000..88fbf95
--- /dev/null
+++ b/change/@griffel-react-3815ac0f-f221-4f2f-b4a6-0d1934fe6657.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: re-export GriffelResetStyle type",
+  "packageName": "@griffel/react",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-webpack-extraction-plugin-b995688a-9700-4878-8832-49047eed3bb3.json b/change/@griffel-webpack-extraction-plugin-b995688a-9700-4878-8832-49047eed3bb3.json
deleted file mode 100644
index a6a1e7e..0000000
--- a/change/@griffel-webpack-extraction-plugin-b995688a-9700-4878-8832-49047eed3bb3.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "patch",
-  "comment": "fix: use style buckets in CSS extraction",
-  "packageName": "@griffel/webpack-extraction-plugin",
-  "email": "olfedias@microsoft.com",
-  "dependentChangeType": "patch"
-}
diff --git a/package.json b/package.json
index 421c7e3..e44961c 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,7 @@
     "jest": "27.2.3",
     "jest-chrome": "^0.7.2",
     "js-beautify": "^1.14.0",
-    "log-symbols": "5.1.0",
+    "log-symbols": "4.1.0",
     "mdx-mermaid": "1.3.2",
     "mermaid": "9.1.7",
     "mini-css-extract-plugin": "2.6.0",
@@ -121,6 +121,7 @@
     "@emotion/hash": "^0.8.0",
     "@linaria/babel-preset": "^3.0.0-beta.23",
     "@linaria/shaker": "^3.0.0-beta.22",
+    "@swc/core": "^1.3.19",
     "@typescript-eslint/utils": "^5.20.0",
     "ajv": "^8.4.0",
     "browserslist": "^4.19.1",
@@ -131,6 +132,7 @@
     "schema-utils": "^3.1.1",
     "source-map-js": "1.0.2",
     "stylis": "^4.0.13",
+    "swc-node": "1.0.0",
     "tslib": "^2.1.0"
   },
   "resolutions": {
diff --git a/packages/babel-preset/CHANGELOG.json b/packages/babel-preset/CHANGELOG.json
index 62735ee..52d3b8a 100644
--- a/packages/babel-preset/CHANGELOG.json
+++ b/packages/babel-preset/CHANGELOG.json
@@ -1,6 +1,27 @@
 {
   "name": "@griffel/babel-preset",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/babel-preset_v1.4.4",
+      "version": "1.4.4",
+      "comments": {
+        "patch": [
+          {
+            "author": "humberto_makoto@hotmail.com",
+            "package": "@griffel/babel-preset",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37",
+            "comment": "fix: Handle arrays in `animationName` definitions."
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/babel-preset",
+            "comment": "Bump @griffel/core to v1.8.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/babel-preset_v1.4.3",
diff --git a/packages/babel-preset/CHANGELOG.md b/packages/babel-preset/CHANGELOG.md
index 1dfd352..a0dca42 100644
--- a/packages/babel-preset/CHANGELOG.md
+++ b/packages/babel-preset/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Change Log - @griffel/babel-preset
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.4.4
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- fix: Handle arrays in `animationName` definitions. (humberto_makoto@hotmail.com)
+- Bump @griffel/core to v1.8.2
+
 ## 1.4.3
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/babel-preset/__fixtures__/keyframes/code.ts b/packages/babel-preset/__fixtures__/keyframes/code.ts
new file mode 100644
index 0000000..bffd271
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/keyframes/code.ts
@@ -0,0 +1,22 @@
+import { makeStyles } from '@griffel/react';
+
+export const useStyles = makeStyles({
+  single: {
+    animationName: {
+      from: { transform: 'rotate(0deg)' },
+      to: { transform: 'rotate(360deg)' },
+    },
+  },
+  multiple: {
+    animationName: [
+      {
+        from: { transform: 'rotate(0deg)' },
+        to: { transform: 'rotate(360deg)' },
+      },
+      {
+        from: { height: '100px' },
+        to: { height: '200px' },
+      },
+    ],
+  },
+});
diff --git a/packages/babel-preset/__fixtures__/keyframes/output.ts b/packages/babel-preset/__fixtures__/keyframes/output.ts
new file mode 100644
index 0000000..d05166e
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/keyframes/output.ts
@@ -0,0 +1,27 @@
+import { __styles } from '@griffel/react';
+export const useStyles = __styles(
+  {
+    single: {
+      Bv12yb3: ['f1g6ul6r', 'f1fp4ujf'],
+    },
+    multiple: {
+      Bv12yb3: ['f1e467oh', 'f1w9bd63'],
+    },
+  },
+  {
+    k: [
+      '@-webkit-keyframes f1q8eu9e{from{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}',
+      '@-webkit-keyframes f55c0se{from{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}to{-webkit-transform:rotate(-360deg);-moz-transform:rotate(-360deg);-ms-transform:rotate(-360deg);transform:rotate(-360deg);}}',
+      '@keyframes f1q8eu9e{from{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}',
+      '@keyframes f55c0se{from{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}to{-webkit-transform:rotate(-360deg);-moz-transform:rotate(-360deg);-ms-transform:rotate(-360deg);transform:rotate(-360deg);}}',
+      '@-webkit-keyframes f19fnzg9{from{height:100px;}to{height:200px;}}',
+      '@keyframes f19fnzg9{from{height:100px;}to{height:200px;}}',
+    ],
+    d: [
+      '.f1g6ul6r{-webkit-animation-name:f1q8eu9e;animation-name:f1q8eu9e;}',
+      '.f1fp4ujf{-webkit-animation-name:f55c0se;animation-name:f55c0se;}',
+      '.f1e467oh{-webkit-animation-name:f1q8eu9e,f19fnzg9;animation-name:f1q8eu9e,f19fnzg9;}',
+      '.f1w9bd63{-webkit-animation-name:f55c0se,f19fnzg9;animation-name:f55c0se,f19fnzg9;}',
+    ],
+  },
+);
diff --git a/packages/babel-preset/package.json b/packages/babel-preset/package.json
index 816d881..e66f4ac 100644
--- a/packages/babel-preset/package.json
+++ b/packages/babel-preset/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/babel-preset",
-  "version": "1.4.3",
+  "version": "1.4.4",
   "description": "Babel preset with build time transforms for Griffel",
   "license": "MIT",
   "repository": {
@@ -20,6 +20,6 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.8.1"
+    "@griffel/core": "^1.8.2"
   }
 }
diff --git a/packages/babel-preset/src/assets/normalizeStyleRules.ts b/packages/babel-preset/src/assets/normalizeStyleRules.ts
index 8247a34..1667a8f 100644
--- a/packages/babel-preset/src/assets/normalizeStyleRules.ts
+++ b/packages/babel-preset/src/assets/normalizeStyleRules.ts
@@ -1,4 +1,4 @@
-import { GriffelStyle } from '@griffel/core';
+import { GriffelAnimation, GriffelStyle } from '@griffel/core';
 import { tokenize } from 'stylis';
 
 import { isAssetUrl } from './isAssetUrl';
@@ -54,9 +54,18 @@ export function normalizeStyleRules(
         return [key, value];
       }
 
-      // Fallback value
+      // Fallback values or keyframes
       if (Array.isArray(value)) {
-        return [key, value.map(rule => normalizeStyleRule(path, projectRoot, filename, rule as string))];
+        return [
+          key,
+          value.map(rule => {
+            if (typeof rule === 'object') {
+              return normalizeStyleRules(path, projectRoot, filename, rule as GriffelAnimation);
+            }
+
+            return normalizeStyleRule(path, projectRoot, filename, rule);
+          }),
+        ];
       }
 
       // Nested objects
diff --git a/packages/core/CHANGELOG.json b/packages/core/CHANGELOG.json
index 20235b7..f0592d8 100644
--- a/packages/core/CHANGELOG.json
+++ b/packages/core/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/core",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/core_v1.8.2",
+      "version": "1.8.2",
+      "comments": {
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/core",
+            "commit": "ebd004e9e8935e099c1873bf2d4841c773bb8417",
+            "comment": "chore: export \"CSSBucketEntry\" type for internal usage"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/core_v1.8.1",
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 6ef8912..339d7b4 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/core
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.8.2
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- chore: export "CSSBucketEntry" type for internal usage (olfedias@microsoft.com)
+
 ## 1.8.1
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/core/package.json b/packages/core/package.json
index d5dcaf2..262e90a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/core",
-  "version": "1.8.1",
+  "version": "1.8.2",
   "description": "DOM implementation of Atomic CSS-in-JS",
   "license": "MIT",
   "repository": {
diff --git a/packages/devtools/CHANGELOG.json b/packages/devtools/CHANGELOG.json
index 34eb125..ebce8c5 100644
--- a/packages/devtools/CHANGELOG.json
+++ b/packages/devtools/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/devtools",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/devtools_v0.2.5",
+      "version": "0.2.5",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/devtools",
+            "comment": "Bump @griffel/react to v1.4.3",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/devtools_v0.2.4",
diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md
index 2ccbec2..e8278a0 100644
--- a/packages/devtools/CHANGELOG.md
+++ b/packages/devtools/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/devtools
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 0.2.5
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- Bump @griffel/react to v1.4.3
+
 ## 0.2.4
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/devtools/package.json b/packages/devtools/package.json
index b7ba383..1c98053 100644
--- a/packages/devtools/package.json
+++ b/packages/devtools/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/devtools",
-  "version": "0.2.4",
+  "version": "0.2.5",
   "description": "Griffel chrome devtools extension",
   "license": "MIT",
   "repository": {
@@ -8,6 +8,6 @@
     "url": "https://github.com/microsoft/griffel"
   },
   "devDependencies": {
-    "@griffel/react": "^1.4.2"
+    "@griffel/react": "^1.4.3"
   }
 }
diff --git a/packages/jest-serializer/CHANGELOG.json b/packages/jest-serializer/CHANGELOG.json
index 49c2e92..9db39d8 100644
--- a/packages/jest-serializer/CHANGELOG.json
+++ b/packages/jest-serializer/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/jest-serializer",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/jest-serializer_v1.1.2",
+      "version": "1.1.2",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/jest-serializer",
+            "comment": "Bump @griffel/core to v1.8.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Thu, 27 Oct 2022 11:38:11 GMT",
       "tag": "@griffel/jest-serializer_v1.1.1",
diff --git a/packages/jest-serializer/CHANGELOG.md b/packages/jest-serializer/CHANGELOG.md
index 274b5a5..cc968be 100644
--- a/packages/jest-serializer/CHANGELOG.md
+++ b/packages/jest-serializer/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/jest-serializer
 
-This log was last generated on Thu, 27 Oct 2022 11:38:11 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.1.2
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.8.2
+
 ## 1.1.1
 
 Thu, 27 Oct 2022 11:38:11 GMT
diff --git a/packages/jest-serializer/package.json b/packages/jest-serializer/package.json
index 887bfeb..3411d8c 100644
--- a/packages/jest-serializer/package.json
+++ b/packages/jest-serializer/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/jest-serializer",
-  "version": "1.1.1",
+  "version": "1.1.2",
   "description": "Jest serializer for Griffel",
   "license": "MIT",
   "repository": {
@@ -11,6 +11,6 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.8.1"
+    "@griffel/core": "^1.8.2"
   }
 }
diff --git a/packages/next-extraction-plugin/CHANGELOG.json b/packages/next-extraction-plugin/CHANGELOG.json
index 9ed5128..9947756 100644
--- a/packages/next-extraction-plugin/CHANGELOG.json
+++ b/packages/next-extraction-plugin/CHANGELOG.json
@@ -1,6 +1,27 @@
 {
   "name": "@griffel/next-extraction-plugin",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/next-extraction-plugin_v0.0.8",
+      "version": "0.0.8",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/next-extraction-plugin",
+            "comment": "Bump @griffel/webpack-extraction-plugin to v0.2.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/next-extraction-plugin",
+            "comment": "Bump @griffel/core to v1.8.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/next-extraction-plugin_v0.0.7",
diff --git a/packages/next-extraction-plugin/CHANGELOG.md b/packages/next-extraction-plugin/CHANGELOG.md
index 99a6374..3d77e68 100644
--- a/packages/next-extraction-plugin/CHANGELOG.md
+++ b/packages/next-extraction-plugin/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Change Log - @griffel/next-extraction-plugin
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 0.0.8
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- Bump @griffel/webpack-extraction-plugin to v0.2.2
+- Bump @griffel/core to v1.8.2
+
 ## 0.0.7
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/next-extraction-plugin/package.json b/packages/next-extraction-plugin/package.json
index e35b3f8..25776ad 100644
--- a/packages/next-extraction-plugin/package.json
+++ b/packages/next-extraction-plugin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/next-extraction-plugin",
-  "version": "0.0.7",
+  "version": "0.0.8",
   "description": "NextJS plugin that add Griffel's CSS extraction to webpack loaders pipeline",
   "license": "MIT",
   "repository": {
@@ -8,11 +8,11 @@
     "url": "https://github.com/microsoft/griffel"
   },
   "dependencies": {
-    "@griffel/webpack-extraction-plugin": "^0.2.1",
+    "@griffel/webpack-extraction-plugin": "^0.2.2",
     "browserslist": "^4.19.1"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.8.1",
+    "@griffel/core": "^1.8.2",
     "next": "^12.0.5",
     "webpack": "^5"
   }
diff --git a/packages/react/CHANGELOG.json b/packages/react/CHANGELOG.json
index 1af89e9..f6278ab 100644
--- a/packages/react/CHANGELOG.json
+++ b/packages/react/CHANGELOG.json
@@ -1,6 +1,21 @@
 {
   "name": "@griffel/react",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/react_v1.4.3",
+      "version": "1.4.3",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/react",
+            "comment": "Bump @griffel/core to v1.8.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/react_v1.4.2",
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index d2e192b..c1c8677 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,9 +1,17 @@
 # Change Log - @griffel/react
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 1.4.3
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- Bump @griffel/core to v1.8.2
+
 ## 1.4.2
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/react/package.json b/packages/react/package.json
index 6fa2067..77a3b9a 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/react",
-  "version": "1.4.2",
+  "version": "1.4.3",
   "description": "React implementation of Atomic CSS-in-JS",
   "license": "MIT",
   "repository": {
@@ -9,7 +9,7 @@
   },
   "sideEffects": false,
   "dependencies": {
-    "@griffel/core": "^1.8.1",
+    "@griffel/core": "^1.8.2",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 1ff8e01..30c91b9 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, CreateDOMRendererOptions, GriffelRenderer } from '@griffel/core';
+export type { GriffelStyle, GriffelResetStyle, CreateDOMRendererOptions, GriffelRenderer } from '@griffel/core';
 
 export { makeStyles } from './makeStyles';
 export { makeResetStyles } from './makeResetStyles';
diff --git a/packages/webpack-extraction-plugin/CHANGELOG.json b/packages/webpack-extraction-plugin/CHANGELOG.json
index b901e2d..022a880 100644
--- a/packages/webpack-extraction-plugin/CHANGELOG.json
+++ b/packages/webpack-extraction-plugin/CHANGELOG.json
@@ -1,6 +1,27 @@
 {
   "name": "@griffel/webpack-extraction-plugin",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/webpack-extraction-plugin_v0.2.2",
+      "version": "0.2.2",
+      "comments": {
+        "patch": [
+          {
+            "author": "olfedias@microsoft.com",
+            "package": "@griffel/webpack-extraction-plugin",
+            "commit": "ebd004e9e8935e099c1873bf2d4841c773bb8417",
+            "comment": "fix: use style buckets in CSS extraction"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-extraction-plugin",
+            "comment": "Bump @griffel/core to v1.8.2",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/webpack-extraction-plugin_v0.2.1",
diff --git a/packages/webpack-extraction-plugin/CHANGELOG.md b/packages/webpack-extraction-plugin/CHANGELOG.md
index ea3879b..e254f93 100644
--- a/packages/webpack-extraction-plugin/CHANGELOG.md
+++ b/packages/webpack-extraction-plugin/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Change Log - @griffel/webpack-extraction-plugin
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 0.2.2
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- fix: use style buckets in CSS extraction (olfedias@microsoft.com)
+- Bump @griffel/core to v1.8.2
+
 ## 0.2.1
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/webpack-extraction-plugin/package.json b/packages/webpack-extraction-plugin/package.json
index ac42229..34d0832 100644
--- a/packages/webpack-extraction-plugin/package.json
+++ b/packages/webpack-extraction-plugin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/webpack-extraction-plugin",
-  "version": "0.2.1",
+  "version": "0.2.2",
   "description": "Webpack plugin that performs CSS extraction for Griffel",
   "license": "MIT",
   "repository": {
@@ -16,7 +16,7 @@
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/core": "^1.8.1",
+    "@griffel/core": "^1.8.2",
     "webpack": "^5"
   }
 }
diff --git a/packages/webpack-loader/CHANGELOG.json b/packages/webpack-loader/CHANGELOG.json
index a5d7e1e..eab19f7 100644
--- a/packages/webpack-loader/CHANGELOG.json
+++ b/packages/webpack-loader/CHANGELOG.json
@@ -1,6 +1,27 @@
 {
   "name": "@griffel/webpack-loader",
   "entries": [
+    {
+      "date": "Thu, 24 Nov 2022 10:05:12 GMT",
+      "tag": "@griffel/webpack-loader_v2.1.4",
+      "version": "2.1.4",
+      "comments": {
+        "patch": [
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/babel-preset to v1.4.4",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          },
+          {
+            "author": "beachball",
+            "package": "@griffel/webpack-loader",
+            "comment": "Bump @griffel/react to v1.4.3",
+            "commit": "fc3403a602d5dd0b206ceeb48ca689cb2fdf6d37"
+          }
+        ]
+      }
+    },
     {
       "date": "Wed, 26 Oct 2022 11:06:03 GMT",
       "tag": "@griffel/webpack-loader_v2.1.3",
diff --git a/packages/webpack-loader/CHANGELOG.md b/packages/webpack-loader/CHANGELOG.md
index a2ffff0..16e3c83 100644
--- a/packages/webpack-loader/CHANGELOG.md
+++ b/packages/webpack-loader/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Change Log - @griffel/webpack-loader
 
-This log was last generated on Wed, 26 Oct 2022 11:06:03 GMT and should not be manually modified.
+This log was last generated on Thu, 24 Nov 2022 10:05:12 GMT and should not be manually modified.
 
 <!-- Start content -->
 
+## 2.1.4
+
+Thu, 24 Nov 2022 10:05:12 GMT
+
+### Patches
+
+- Bump @griffel/babel-preset to v1.4.4
+- Bump @griffel/react to v1.4.3
+
 ## 2.1.3
 
 Wed, 26 Oct 2022 11:06:03 GMT
diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json
index ac3624f..aa9f5c1 100644
--- a/packages/webpack-loader/package.json
+++ b/packages/webpack-loader/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@griffel/webpack-loader",
-  "version": "2.1.3",
+  "version": "2.1.4",
   "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.4.3",
+    "@griffel/babel-preset": "^1.4.4",
     "enhanced-resolve": "^5.8.2",
     "loader-utils": "^2.0.0",
     "schema-utils": "^3.1.1",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
-    "@griffel/react": "^1.4.2",
+    "@griffel/react": "^1.4.3",
     "webpack": "^5"
   }
 }
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 113872b..3ce9505 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -18,7 +18,7 @@
       "@griffel/babel-preset": ["packages/babel-preset/src/index.ts"],
       "@griffel/core": ["packages/core/src/index.ts"],
       "@griffel/devtools": ["packages/devtools/src/index.ts"],
-      "@griffel/e2e-typescript": ["e2e/typescript/src/index.ts"],
+      "@griffel/e2e-utils": ["e2e/utils/src/index.ts"],
       "@griffel/eslint-plugin": ["packages/eslint-plugin/src/index.ts"],
       "@griffel/jest-serializer": ["packages/jest-serializer/src/index.ts"],
       "@griffel/next-extraction-plugin": ["packages/next-extraction-plugin/src/index.ts"],
diff --git a/workspace.json b/workspace.json
index 59601ec..a3949ba 100644
--- a/workspace.json
+++ b/workspace.json
@@ -5,6 +5,8 @@
     "@griffel/babel-preset": "packages/babel-preset",
     "@griffel/core": "packages/core",
     "@griffel/devtools": "packages/devtools",
+    "@griffel/e2e-nextjs": "e2e/nextjs",
+    "@griffel/e2e-utils": "e2e/utils",
     "@griffel/e2e-typescript": "e2e/typescript",
     "@griffel/eslint-plugin": "packages/eslint-plugin",
     "@griffel/jest-serializer": "packages/jest-serializer",
diff --git a/yarn.lock b/yarn.lock
index 48e5b2d..f96ecea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5900,6 +5900,153 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@swc-node/core@npm:^1.9.1":
+  version: 1.9.1
+  resolution: "@swc-node/core@npm:1.9.1"
+  peerDependencies:
+    "@swc/core": ">= 1.3"
+  checksum: 4e76fd36002a64aa386288771ddc2b3c0e9a0e09bb8a4fc32d3e358181ad4dc8b945658cc47af036ba0bff4320b347db10191f8ffce73069be7e17f9c5745859
+  languageName: node
+  linkType: hard
+
+"@swc-node/register@npm:^1.4.0":
+  version: 1.5.4
+  resolution: "@swc-node/register@npm:1.5.4"
+  dependencies:
+    "@swc-node/core": ^1.9.1
+    "@swc-node/sourcemap-support": ^0.2.2
+    colorette: ^2.0.19
+    debug: ^4.3.4
+    pirates: ^4.0.5
+    tslib: ^2.4.0
+  peerDependencies:
+    "@swc/core": ">= 1.3"
+    typescript: ">= 4.3"
+  checksum: 1ebb39fb36576a96700f8a0c6d0a5f09dfcf4a0a3d5e81b3ae9ab44de6f9469a65463864992451fdcacce6670c95c82f2d74ccc439f4f6d1bd14bdede785ec2e
+  languageName: node
+  linkType: hard
+
+"@swc-node/sourcemap-support@npm:^0.2.2":
+  version: 0.2.2
+  resolution: "@swc-node/sourcemap-support@npm:0.2.2"
+  dependencies:
+    source-map-support: ^0.5.21
+    tslib: ^2.4.0
+  checksum: d0c0fba3e8f95fa3d42063ad1b757293caf1a32a92b2589a52e9f602d6a5f321b8ab2c87cb324470dd60b00fcb99bee5b38db0f4ab09e15360712fa5afad8a8c
+  languageName: node
+  linkType: hard
+
+"@swc/core-darwin-arm64@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-darwin-arm64@npm:1.3.19"
+  conditions: os=darwin & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@swc/core-darwin-x64@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-darwin-x64@npm:1.3.19"
+  conditions: os=darwin & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@swc/core-linux-arm-gnueabihf@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.19"
+  conditions: os=linux & cpu=arm
+  languageName: node
+  linkType: hard
+
+"@swc/core-linux-arm64-gnu@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-linux-arm64-gnu@npm:1.3.19"
+  conditions: os=linux & cpu=arm64 & libc=glibc
+  languageName: node
+  linkType: hard
+
+"@swc/core-linux-arm64-musl@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-linux-arm64-musl@npm:1.3.19"
+  conditions: os=linux & cpu=arm64 & libc=musl
+  languageName: node
+  linkType: hard
+
+"@swc/core-linux-x64-gnu@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-linux-x64-gnu@npm:1.3.19"
+  conditions: os=linux & cpu=x64 & libc=glibc
+  languageName: node
+  linkType: hard
+
+"@swc/core-linux-x64-musl@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-linux-x64-musl@npm:1.3.19"
+  conditions: os=linux & cpu=x64 & libc=musl
+  languageName: node
+  linkType: hard
+
+"@swc/core-win32-arm64-msvc@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-win32-arm64-msvc@npm:1.3.19"
+  conditions: os=win32 & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@swc/core-win32-ia32-msvc@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-win32-ia32-msvc@npm:1.3.19"
+  conditions: os=win32 & cpu=ia32
+  languageName: node
+  linkType: hard
+
+"@swc/core-win32-x64-msvc@npm:1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core-win32-x64-msvc@npm:1.3.19"
+  conditions: os=win32 & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@swc/core@npm:^1.3.19":
+  version: 1.3.19
+  resolution: "@swc/core@npm:1.3.19"
+  dependencies:
+    "@swc/core-darwin-arm64": 1.3.19
+    "@swc/core-darwin-x64": 1.3.19
+    "@swc/core-linux-arm-gnueabihf": 1.3.19
+    "@swc/core-linux-arm64-gnu": 1.3.19
+    "@swc/core-linux-arm64-musl": 1.3.19
+    "@swc/core-linux-x64-gnu": 1.3.19
+    "@swc/core-linux-x64-musl": 1.3.19
+    "@swc/core-win32-arm64-msvc": 1.3.19
+    "@swc/core-win32-ia32-msvc": 1.3.19
+    "@swc/core-win32-x64-msvc": 1.3.19
+  dependenciesMeta:
+    "@swc/core-darwin-arm64":
+      optional: true
+    "@swc/core-darwin-x64":
+      optional: true
+    "@swc/core-linux-arm-gnueabihf":
+      optional: true
+    "@swc/core-linux-arm64-gnu":
+      optional: true
+    "@swc/core-linux-arm64-musl":
+      optional: true
+    "@swc/core-linux-x64-gnu":
+      optional: true
+    "@swc/core-linux-x64-musl":
+      optional: true
+    "@swc/core-win32-arm64-msvc":
+      optional: true
+    "@swc/core-win32-ia32-msvc":
+      optional: true
+    "@swc/core-win32-x64-msvc":
+      optional: true
+  bin:
+    swcx: run_swcx.js
+  checksum: 752499e18f81df789a9737936b2a83ea1db34bb2f983c80b394766796d853fa7d30701b52b9cb30ba943ad15286d8316603149683f4c553a5493aa78d89a76e0
+  languageName: node
+  linkType: hard
+
 "@swc/helpers@npm:0.4.3":
   version: 0.4.3
   resolution: "@swc/helpers@npm:0.4.3"
@@ -9344,13 +9491,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chalk@npm:^5.0.0":
-  version: 5.0.0
-  resolution: "chalk@npm:5.0.0"
-  checksum: 6eba7c518b9aa5fe882ae6d14a1ffa58c418d72a3faa7f72af56641f1bbef51b645fca1d6e05d42357b7d3c846cd504c0b7b64d12309cdd07867e3b4411e0d01
-  languageName: node
-  linkType: hard
-
 "char-regex@npm:^1.0.2":
   version: 1.0.2
   resolution: "char-regex@npm:1.0.2"
@@ -9780,10 +9920,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"colorette@npm:^2.0.10":
-  version: 2.0.16
-  resolution: "colorette@npm:2.0.16"
-  checksum: cd55596a3a2d1071c1a28eee7fd8a5387593ff1bd10a3e8d0a6221499311fe34a9f2b9272d77c391e0e003dcdc8934fb2f8d106e7ef1f7516f8060c901d41a27
+"colorette@npm:^2.0.10, colorette@npm:^2.0.19":
+  version: 2.0.19
+  resolution: "colorette@npm:2.0.19"
+  checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427
   languageName: node
   linkType: hard
 
@@ -14547,6 +14687,7 @@ __metadata:
     "@storybook/manager-webpack5": ~6.4.5
     "@storybook/react": ~6.4.5
     "@svgr/webpack": ^5.4.0
+    "@swc/core": ^1.3.19
     "@testing-library/jest-dom": 5.16.1
     "@testing-library/react": 12.1.2
     "@testing-library/react-hooks": 7.0.2
@@ -14596,7 +14737,7 @@ __metadata:
     jest-chrome: ^0.7.2
     js-beautify: ^1.14.0
     loader-utils: ^2.0.0
-    log-symbols: 5.1.0
+    log-symbols: 4.1.0
     mdx-mermaid: 1.3.2
     mermaid: 9.1.7
     mini-css-extract-plugin: 2.6.0
@@ -14614,6 +14755,7 @@ __metadata:
     simple-git-hooks: 2.7.0
     source-map-js: 1.0.2
     stylis: ^4.0.13
+    swc-node: 1.0.0
     syncpack: 6.2.0
     tmp: 0.2.1
     ts-jest: 27.0.5
@@ -16153,10 +16295,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-unicode-supported@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "is-unicode-supported@npm:1.1.0"
-  checksum: 1f2504d94383ea180ea25e729b40b1d97398a37325c2e62db96a3e98b457c767259dd5bbf9ab2815e83e5012dc4b61d533e75d12df7f208c470474d821bd5f24
+"is-unicode-supported@npm:^0.1.0":
+  version: 0.1.0
+  resolution: "is-unicode-supported@npm:0.1.0"
+  checksum: a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52
   languageName: node
   linkType: hard
 
@@ -17709,13 +17851,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"log-symbols@npm:5.1.0":
-  version: 5.1.0
-  resolution: "log-symbols@npm:5.1.0"
+"log-symbols@npm:4.1.0":
+  version: 4.1.0
+  resolution: "log-symbols@npm:4.1.0"
   dependencies:
-    chalk: ^5.0.0
-    is-unicode-supported: ^1.1.0
-  checksum: 7291b6e7f1b3df6865bdaeb9b59605c832668ac2fa0965c63b1e7dd3700349aec09c1d7d40c368d5041ff58b7f89461a56e4009471921301af7b3609cbff9a29
+    chalk: ^4.1.0
+    is-unicode-supported: ^0.1.0
+  checksum: fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74
   languageName: node
   linkType: hard
 
@@ -23292,7 +23434,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.17, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20":
+"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.17, source-map-support@npm:^0.5.21, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20":
   version: 0.5.21
   resolution: "source-map-support@npm:0.5.21"
   dependencies:
@@ -24031,6 +24173,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"swc-node@npm:1.0.0":
+  version: 1.0.0
+  resolution: "swc-node@npm:1.0.0"
+  dependencies:
+    "@swc-node/register": ^1.4.0
+  bin:
+    swc-node: bin/swc-node
+  checksum: 08602996e917b0f23bc0f2f7d5095bf65d3e057664a2849aeb88e40dcf465fcec9a3a7566f1bd1d1a81e2c57d90a5ea09fa0fbff14a9d3a286139eed64519257
+  languageName: node
+  linkType: hard
+
 "symbol-tree@npm:^3.2.4":
   version: 3.2.4
   resolution: "symbol-tree@npm:3.2.4"


Test patch:
diff --git a/e2e/nextjs/.eslintrc.json b/e2e/nextjs/.eslintrc.json
new file mode 100644
index 0000000..9d9c0db
--- /dev/null
+++ b/e2e/nextjs/.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/nextjs/README.md b/e2e/nextjs/README.md
new file mode 100644
index 0000000..a164d5a
--- /dev/null
+++ b/e2e/nextjs/README.md
@@ -0,0 +1,3 @@
+This test package was created to ensure that CSS extraction remains compatible with Next.js.
+
+The test uses `@griffel/next-extraction-plugin` and the recommended config from our docs.
diff --git a/e2e/nextjs/project.json b/e2e/nextjs/project.json
new file mode 100644
index 0000000..fc66c8f
--- /dev/null
+++ b/e2e/nextjs/project.json
@@ -0,0 +1,25 @@
+{
+  "root": "e2e/nextjs",
+  "sourceRoot": "e2e/nextjs/src",
+  "projectType": "library",
+  "targets": {
+    "test": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "e2e/nextjs",
+        "dependsOn": [{ "target": "build", "projects": "dependencies" }],
+        "commands": [{ "command": "swc-node src/test.ts" }],
+        "outputPath": []
+      }
+    },
+    "type-check": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "e2e/nextjs",
+        "commands": [{ "command": "tsc -b --pretty" }],
+        "outputPath": []
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/e2e/nextjs/src/assets/next.config.js b/e2e/nextjs/src/assets/next.config.js
new file mode 100644
index 0000000..57453d7
--- /dev/null
+++ b/e2e/nextjs/src/assets/next.config.js
@@ -0,0 +1,33 @@
+const { withGriffelCSSExtraction } = require('@griffel/next-extraction-plugin');
+
+module.exports = withGriffelCSSExtraction()({
+  webpack(config) {
+    config.module.rules.unshift({
+      test: /\.(js|jsx)$/,
+      exclude: /node_modules/,
+      use: [
+        {
+          loader: '@griffel/webpack-loader',
+        },
+      ],
+    });
+
+    // If your project uses TypeScript
+    config.module.rules.unshift({
+      test: /\.(ts|tsx)$/,
+      exclude: /node_modules/,
+      use: [
+        {
+          loader: '@griffel/webpack-loader',
+          options: {
+            babelOptions: {
+              presets: ['next/babel'],
+            },
+          },
+        },
+      ],
+    });
+
+    return config;
+  },
+});
diff --git a/e2e/nextjs/src/assets/pages/_app.tsx b/e2e/nextjs/src/assets/pages/_app.tsx
new file mode 100644
index 0000000..86e27cd
--- /dev/null
+++ b/e2e/nextjs/src/assets/pages/_app.tsx
@@ -0,0 +1,16 @@
+import { createDOMRenderer, GriffelRenderer, RendererProvider } from '@griffel/react';
+import * as React from 'react';
+
+import type { AppProps } from 'next/app';
+
+type EnhancedAppProps = AppProps & { renderer?: GriffelRenderer };
+
+function MyApp({ Component, pageProps, renderer }: EnhancedAppProps) {
+  return (
+    <RendererProvider renderer={renderer || createDOMRenderer()}>
+      <Component {...pageProps} />
+    </RendererProvider>
+  );
+}
+
+export default MyApp;
diff --git a/e2e/nextjs/src/assets/pages/_document.tsx b/e2e/nextjs/src/assets/pages/_document.tsx
new file mode 100644
index 0000000..49163c8
--- /dev/null
+++ b/e2e/nextjs/src/assets/pages/_document.tsx
@@ -0,0 +1,53 @@
+import { createDOMRenderer, renderToStyleElements } from '@griffel/react';
+import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
+import * as React from 'react';
+
+class MyDocument extends Document {
+  static override async getInitialProps(ctx: DocumentContext) {
+    // 👇 creates a renderer that will be used for SSR
+    const renderer = createDOMRenderer();
+    const originalRenderPage = ctx.renderPage;
+
+    ctx.renderPage = () =>
+      originalRenderPage({
+        enhanceApp: App =>
+          function EnhancedApp(props) {
+            const enhancedProps = {
+              ...props,
+              // 👇 this is required to provide a proper renderer instance
+              renderer,
+            };
+
+            return <App {...enhancedProps} />;
+          },
+      });
+
+    const initialProps = await Document.getInitialProps(ctx);
+    const styles = renderToStyleElements(renderer);
+
+    return {
+      ...initialProps,
+      styles: (
+        <>
+          {initialProps.styles}
+          {/* 👇 adding Griffel styles elements to output */}
+          {styles}
+        </>
+      ),
+    };
+  }
+
+  override render() {
+    return (
+      <Html>
+        <Head />
+        <body>
+          <Main />
+          <NextScript />
+        </body>
+      </Html>
+    );
+  }
+}
+
+export default MyDocument;
diff --git a/e2e/nextjs/src/assets/pages/index.tsx b/e2e/nextjs/src/assets/pages/index.tsx
new file mode 100644
index 0000000..25db75e
--- /dev/null
+++ b/e2e/nextjs/src/assets/pages/index.tsx
@@ -0,0 +1,34 @@
+import { makeStyles, shorthands } from '@griffel/react';
+import Head from 'next/head';
+import * as React from 'react';
+
+import type { NextPage } from 'next';
+
+const useStyles = makeStyles({
+  container: {
+    display: 'flex',
+    flexDirection: 'column',
+    width: '200px',
+
+    ...shorthands.border('2px', 'dashed', 'magenta'),
+    ...shorthands.borderRadius('5px'),
+    ...shorthands.gap('5px'),
+    ...shorthands.padding('10px'),
+  },
+});
+
+const Home: NextPage = () => {
+  const styles = useStyles();
+
+  return (
+    <>
+      <Head>
+        <title>My app</title>
+      </Head>
+
+      <div className={styles.container}>Hello world!</div>
+    </>
+  );
+};
+
+export default Home;
diff --git a/e2e/nextjs/src/snapshots/output.css b/e2e/nextjs/src/snapshots/output.css
new file mode 100644
index 0000000..2be23d6
--- /dev/null
+++ b/e2e/nextjs/src/snapshots/output.css
@@ -0,0 +1,76 @@
+.f22iagw {
+  display: flex;
+}
+.f1vx9l62 {
+  flex-direction: column;
+}
+.f45u8dm {
+  width: 200px;
+}
+.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: #f0f;
+}
+.f1g7g05w {
+  border-right-color: #f0f;
+}
+.f10ibvnv {
+  border-left-color: #f0f;
+}
+.fa31z3g {
+  border-bottom-color: #f0f;
+}
+.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 {
+  -moz-column-gap: 5px;
+  column-gap: 5px;
+}
+.ffmv8ov {
+  row-gap: 5px;
+}
+.f1809wu7 {
+  padding-top: 10px;
+}
+.f81rol6 {
+  padding-right: 10px;
+}
+.frdkuqy {
+  padding-left: 10px;
+}
+.f1fow5ox {
+  padding-bottom: 10px;
+}
diff --git a/e2e/nextjs/src/test.ts b/e2e/nextjs/src/test.ts
new file mode 100644
index 0000000..4cf925e
--- /dev/null
+++ b/e2e/nextjs/src/test.ts
@@ -0,0 +1,96 @@
+import { 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, '..', '..', '..');
+
+  let tempDir: string;
+
+  try {
+    tempDir = createTempDir('nextjs');
+
+    await copyAssets({ assetsPath: path.resolve(__dirname, 'assets'), tempDir });
+    await configureYarn({ tempDir, rootDir });
+
+    const resolutions = await Promise.all([
+      packLocalPackage(rootDir, tempDir, '@griffel/core'),
+      packLocalPackage(rootDir, tempDir, '@griffel/react'),
+      packLocalPackage(rootDir, tempDir, '@griffel/webpack-loader'),
+      packLocalPackage(rootDir, tempDir, '@griffel/next-extraction-plugin'),
+    ]);
+
+    const nextRawVersion = await sh(`yarn next -v`, rootDir, true);
+    const nextVersion = nextRawVersion.split('v')[1].trim();
+
+    console.log(logSymbols.info, 'Using Next.js', nextVersion);
+
+    await installPackages({
+      packages: ['next', 'react', 'react-dom', 'typescript', '@types/react', '@types/node'],
+      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 next build`, tempDir);
+
+    console.log(logSymbols.success, `Example project was successfully built with Next.js`);
+  } catch (e) {
+    console.error(e);
+
+    console.log('');
+    console.error(logSymbols.error, `Building a test project Next.js failed.`);
+
+    process.exit(1);
+  }
+
+  try {
+    const cssFilesPath = path.resolve(tempDir, '.next', 'static', 'css');
+    const cssFiles = await fs.promises.readdir(cssFilesPath);
+
+    if (cssFiles.length === 0) {
+      throw new Error(`Failed to find CSS files in "${cssFilesPath}"`);
+    }
+
+    if (cssFiles.length > 1) {
+      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');
+    }
+
+    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 Next.js build failed.`);
+
+    process.exit(1);
+  }
+}
+
+(async () => {
+  await performTest();
+})();
diff --git a/e2e/nextjs/tsconfig.json b/e2e/nextjs/tsconfig.json
new file mode 100644
index 0000000..5f40288
--- /dev/null
+++ b/e2e/nextjs/tsconfig.json
@@ -0,0 +1,23 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "module": "CommonJS",
+    "jsx": "react",
+    "lib": ["ES2019", "DOM", "DOM.Iterable"],
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "files": [],
+  "include": [],
+  "references": [
+    {
+      "path": "./tsconfig.lib.json"
+    }
+  ]
+}
diff --git a/e2e/nextjs/tsconfig.lib.json b/e2e/nextjs/tsconfig.lib.json
new file mode 100644
index 0000000..7dd571e
--- /dev/null
+++ b/e2e/nextjs/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../dist/out-tsc",
+    "declaration": true,
+    "types": ["node", "environment"]
+  },
+  "include": ["**/*.ts", "**/*.tsx", "**/*.js"]
+}
diff --git a/e2e/typescript/project.json b/e2e/typescript/project.json
index 702aaf4..ba8d2e8 100644
--- a/e2e/typescript/project.json
+++ b/e2e/typescript/project.json
@@ -8,7 +8,7 @@
       "options": {
         "cwd": "e2e/typescript",
         "dependsOn": [{ "target": "build", "projects": "dependencies" }],
-        "commands": [{ "command": "node src/test.mjs" }],
+        "commands": [{ "command": "swc-node src/test.ts" }],
         "outputPath": []
       }
     },
diff --git a/e2e/typescript/src/test.mjs b/e2e/typescript/src/test.mjs
deleted file mode 100644
index 4bc7933..0000000
--- a/e2e/typescript/src/test.mjs
+++ /dev/null
@@ -1,138 +0,0 @@
-import * as childProcess from 'child_process';
-import fs from 'fs';
-import logSymbols from 'log-symbols';
-import path from 'path';
-import tmp from 'tmp';
-
-/**
- * @param {String} command
- * @param {String?} cwd
- * @param {Boolean?} pipeOutputToResult
- *
- * @returns {Promise<string>}
- */
-function sh(command, cwd, pipeOutputToResult = false) {
-  return new Promise((resolve, reject) => {
-    const [cmd, ...args] = command.split(' ');
-
-    /** @type {import('child_process').SpawnOptions} */
-    const options = {
-      cwd: cwd || process.cwd(),
-      env: process.env,
-      stdio: pipeOutputToResult ? 'pipe' : 'inherit',
-      shell: true,
-    };
-
-    const child = childProcess.spawn(cmd, args, options);
-
-    let stdoutData = '';
-
-    if (child.stdout) {
-      child.stdout.on('data', data => {
-        stdoutData += data;
-      });
-    }
-
-    child.on('close', code => {
-      if (code === 0) {
-        resolve(stdoutData);
-      }
-
-      reject(new Error([`child process exited with code ${code}`, stdoutData].join('\n')));
-    });
-  });
-}
-
-/**
- * @param {String} tsVersion
- */
-async function performTest(tsVersion) {
-  /** @type {String} */
-  let tempDir;
-  /** @type {String} */
-  let tscBin;
-
-  try {
-    const dirname = path.dirname(new URL(import.meta.url).pathname);
-    const rootDir = path.resolve(dirname, '..', '..', '..');
-
-    const assetsPath = path.resolve(dirname, 'assets');
-
-    // "Unsafe" means delete even if it still has files inside (our desired behavior)
-    tempDir = tmp.dirSync({ prefix: 'typescript', unsafeCleanup: true }).name;
-
-    console.log(logSymbols.success, `Temporary directory created under ${tempDir}`);
-
-    const packagePath = path.resolve(rootDir, 'dist', 'packages', 'core');
-    const packagePathExists = !!(await fs.promises.stat(packagePath).catch(() => false));
-
-    if (!packagePathExists) {
-      throw new Error(`A directory with artifacts (${packagePath}) does not exist`);
-    }
-
-    // Use `npm pack` because `yarn pack` incorrectly calculates the included files when the
-    // files to include/exclude are specified by .npmignore rather than package.json `files`.
-    // (--quiet outputs only the .tgz filename, not all the included files)
-    const packFile = (await sh(`npm pack --quiet ${packagePath}`, tempDir, true)).trim();
-    console.log(logSymbols.success, 'Package files were packed');
-
-    await fs.promises.copyFile(path.resolve(assetsPath, 'fixture.ts'), path.join(tempDir, 'fixture.ts'));
-    await fs.promises.copyFile(path.resolve(assetsPath, 'fixture-reset.ts'), path.join(tempDir, 'fixture-reset.ts'));
-    await fs.promises.copyFile(path.resolve(assetsPath, 'tsconfig.fixture.json'), path.join(tempDir, 'tsconfig.json'));
-
-    // Reuse the same cache directory to speed up install and avoid network requests
-    const yarnCacheDir = tmp.dirSync({ prefix: 'yarn-cache', unsafeCleanup: true }).name;
-    const yarnPath = await sh('yarn config get yarnPath', dirname, true);
-
-    await fs.promises.writeFile(
-      path.resolve(tempDir, '.yarnrc.yml'),
-      [`cacheFolder: ${yarnCacheDir}`, 'nodeLinker: node-modules', `yarnPath: ${yarnPath}`].join('\n'),
-    );
-    console.log(logSymbols.success, 'A fixture and configs were copied');
-
-    console.log(logSymbols.info, 'Using Yarn', (await sh('yarn --version', tempDir, true)).trim());
-
-    await sh('yarn init -p', tempDir, true);
-    await sh(`yarn add ./${packFile} typescript@${tsVersion}`, tempDir, true);
-
-    console.log(logSymbols.success, 'Packages were installed');
-
-    tscBin = path.resolve(tempDir, 'node_modules', 'typescript', 'bin', 'tsc');
-    console.log(
-      logSymbols.info,
-      'Using TypeScript',
-      (await sh(`${tscBin} --version`, tempDir, true)).replace('Version', '').trim(),
-    );
-  } catch (e) {
-    console.error(logSymbols.error, 'Something went wrong setting up the test:');
-    console.error(/** @type {Error} */ e?.stack ?? e);
-    process.exit(1);
-  }
-
-  try {
-    await sh(`${tscBin} --noEmit --pretty`, tempDir);
-
-    console.log(logSymbols.success, `Example project was successfully built with typescript@${tsVersion}`);
-    console.log('');
-    console.log('');
-  } catch (e) {
-    console.error(e);
-
-    console.log('');
-    console.error(
-      logSymbols.error,
-      `Building a test project referencing @griffel/core using typescript@${tsVersion} failed.`,
-    );
-    console.error(
-      `This is most likely because you added an API in @griffel/core or a dependency which uses ` +
-        `typescript features introduced in a version newer than ${tsVersion} (see logs above for the exact error).`,
-    );
-    process.exit(1);
-  }
-}
-
-(async () => {
-  await performTest('3.9');
-  await performTest('4.1');
-  await performTest('4.4');
-})();
diff --git a/e2e/typescript/src/test.ts b/e2e/typescript/src/test.ts
new file mode 100644
index 0000000..e0bddcd
--- /dev/null
+++ b/e2e/typescript/src/test.ts
@@ -0,0 +1,71 @@
+import { configureYarn, copyAssets, createTempDir, installPackages, packLocalPackage, sh } from '@griffel/e2e-utils';
+import * as logSymbols from 'log-symbols';
+import * as path from 'path';
+
+async function performTest(tsVersion: string) {
+  let tempDir: string;
+  let tscBin: string;
+
+  try {
+    const rootDir = path.resolve(__dirname, '..', '..', '..');
+
+    tempDir = createTempDir('typescript');
+
+    await copyAssets({
+      assetsPath: path.resolve(__dirname, 'assets'),
+      tempDir,
+      renames: { 'tsconfig.fixture.json': 'tsconfig.json' },
+    });
+    await configureYarn({ tempDir, rootDir });
+
+    const resolutions = [
+      await packLocalPackage(rootDir, tempDir, '@griffel/core'),
+      { packageName: 'typescript', version: tsVersion },
+    ];
+
+    await installPackages({
+      packages: [],
+      resolutions,
+      tempDir,
+      rootDir,
+    });
+
+    tscBin = path.resolve(tempDir, 'node_modules', 'typescript', 'bin', 'tsc');
+    console.log(
+      logSymbols.info,
+      'Using TypeScript',
+      (await sh(`${tscBin} --version`, tempDir, true)).replace('Version', '').trim(),
+    );
+  } 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(`${tscBin} --noEmit --pretty`, tempDir);
+
+    console.log(logSymbols.success, `Example project was successfully built with typescript@${tsVersion}`);
+    console.log('');
+    console.log('');
+  } catch (e) {
+    console.error(e);
+
+    console.log('');
+    console.error(
+      logSymbols.error,
+      `Building a test project referencing @griffel/core using typescript@${tsVersion} failed.`,
+    );
+    console.error(
+      `This is most likely because you added an API in @griffel/core or a dependency which uses ` +
+        `typescript features introduced in a version newer than ${tsVersion} (see logs above for the exact error).`,
+    );
+    process.exit(1);
+  }
+}
+
+(async () => {
+  await performTest('3.9');
+  await performTest('4.1');
+  await performTest('4.4');
+})();
diff --git a/e2e/typescript/tsconfig.json b/e2e/typescript/tsconfig.json
index 87ebcca..7e9e1b9 100644
--- a/e2e/typescript/tsconfig.json
+++ b/e2e/typescript/tsconfig.json
@@ -4,6 +4,7 @@
     "allowJs": true,
     "checkJs": true,
     "module": "CommonJS",
+    "lib": ["ES2019", "DOM", "DOM.Iterable"],
     "forceConsistentCasingInFileNames": true,
     "strict": true,
     "noImplicitOverride": true,
diff --git a/e2e/utils/project.json b/e2e/utils/project.json
new file mode 100644
index 0000000..7270650
--- /dev/null
+++ b/e2e/utils/project.json
@@ -0,0 +1,16 @@
+{
+  "root": "e2e/utils",
+  "sourceRoot": "e2e/utils/src",
+  "projectType": "library",
+  "targets": {
+    "type-check": {
+      "executor": "@nrwl/workspace:run-commands",
+      "options": {
+        "cwd": "e2e/utils",
+        "commands": [{ "command": "tsc -b --pretty" }],
+        "outputPath": []
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/e2e/utils/src/configureYarn.ts b/e2e/utils/src/configureYarn.ts
new file mode 100644
index 0000000..a03b6eb
--- /dev/null
+++ b/e2e/utils/src/configureYarn.ts
@@ -0,0 +1,19 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as logSymbols from 'log-symbols';
+
+import { sh } from './sh';
+
+export async function configureYarn(options: { tempDir: string; rootDir: string }) {
+  const { tempDir, rootDir } = options;
+  const yarnPath = await sh('yarn config get yarnPath', rootDir, true);
+
+  await fs.promises.writeFile(
+    path.resolve(tempDir, '.yarnrc.yml'),
+    ['enableImmutableInstalls: false', 'nodeLinker: node-modules', `yarnPath: ${yarnPath}`].join('\n'),
+  );
+  await sh('yarn init -p', tempDir, true);
+
+  console.log(logSymbols.success, 'A config for Yarn was created');
+  console.log(logSymbols.info, 'Using Yarn', (await sh('yarn --version', tempDir, true)).trim());
+}
diff --git a/e2e/utils/src/copyAssets.ts b/e2e/utils/src/copyAssets.ts
new file mode 100644
index 0000000..dc3f029
--- /dev/null
+++ b/e2e/utils/src/copyAssets.ts
@@ -0,0 +1,20 @@
+import * as fs from 'fs';
+import * as fsExtra from 'fs-extra';
+import * as path from 'path';
+import * as logSymbols from 'log-symbols';
+
+export async function copyAssets(options: { assetsPath: string; tempDir: string; renames?: Record<string, string> }) {
+  const { assetsPath, tempDir, renames } = options;
+
+  await fsExtra.copy(assetsPath, tempDir);
+
+  if (renames) {
+    await Promise.all(
+      Object.entries(renames).map(([source, target]) =>
+        fs.promises.rename(path.resolve(tempDir, source), path.resolve(tempDir, target)),
+      ),
+    );
+  }
+
+  console.log(logSymbols.success, 'Assets were copied');
+}
diff --git a/e2e/utils/src/createTempDir.ts b/e2e/utils/src/createTempDir.ts
new file mode 100644
index 0000000..e5e6e8c
--- /dev/null
+++ b/e2e/utils/src/createTempDir.ts
@@ -0,0 +1,10 @@
+import * as tmp from 'tmp';
+import * as logSymbols from 'log-symbols';
+
+export function createTempDir(prefix: string) {
+  // "Unsafe" means delete even if it still has files inside (our desired behavior)
+  const tempDir = tmp.dirSync({ prefix: 'nextjs', unsafeCleanup: true }).name;
+  console.log(logSymbols.success, `Temporary directory created under ${tempDir}`);
+
+  return tempDir;
+}
diff --git a/e2e/utils/src/index.ts b/e2e/utils/src/index.ts
new file mode 100644
index 0000000..5eb898e
--- /dev/null
+++ b/e2e/utils/src/index.ts
@@ -0,0 +1,6 @@
+export { configureYarn } from './configureYarn';
+export { copyAssets } from './copyAssets';
+export { createTempDir } from './createTempDir';
+export { packLocalPackage } from './packLocalPackage';
+export { installPackages } from './installPackages';
+export { sh } from './sh';
diff --git a/e2e/utils/src/installPackages.ts b/e2e/utils/src/installPackages.ts
new file mode 100644
index 0000000..9334af1
--- /dev/null
+++ b/e2e/utils/src/installPackages.ts
@@ -0,0 +1,50 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as logSymbols from 'log-symbols';
+
+import { sh } from './sh';
+
+export async function installPackages(options: {
+  packages: string[];
+  resolutions: { file?: string; version?: string; packageName: string }[];
+  rootDir: string;
+  tempDir: string;
+}) {
+  const { tempDir, packages, resolutions, rootDir } = options;
+
+  const packageJsonPath = path.resolve(tempDir + '/package.json');
+  const packageJsonPathExists = !!(await fs.promises.stat(packageJsonPath).catch(() => false));
+
+  if (!packageJsonPathExists) {
+    throw new Error(`A "package.json" in a temporary directory does not exist`);
+  }
+
+  let packagesWithVersions = {};
+
+  if (packages.length > 0) {
+    const yarnOutput = await sh(`yarn info ${packages.join(' ')} --json`, rootDir, true);
+    const parsedYarnOutput = yarnOutput
+      .split('\n')
+      .filter(Boolean)
+      .map(line => JSON.parse(line));
+
+    packagesWithVersions = Object.fromEntries(
+      parsedYarnOutput.map(info => [info.value.split('@patch')[0].split('@npm')[0], info.children.Version]),
+    );
+  }
+
+  const packageJson = JSON.parse(await fs.promises.readFile(tempDir + '/package.json', 'utf8'));
+  const newPackageJson = {
+    ...packageJson,
+    dependencies: {
+      ...Object.fromEntries(resolutions.map(pkg => [pkg.packageName, '*'])),
+      ...packagesWithVersions,
+    },
+    resolutions: Object.fromEntries(resolutions.map(pkg => [pkg.packageName, pkg.version || `./${pkg.file}`])),
+  };
+
+  await fs.promises.writeFile(packageJsonPath, JSON.stringify(newPackageJson, null, 2));
+  await sh('yarn install', tempDir, true);
+
+  console.log(logSymbols.success, 'Packages were installed');
+}
diff --git a/e2e/utils/src/packLocalPackage.ts b/e2e/utils/src/packLocalPackage.ts
new file mode 100644
index 0000000..f57fcea
--- /dev/null
+++ b/e2e/utils/src/packLocalPackage.ts
@@ -0,0 +1,25 @@
+import * as path from 'path';
+import * as fs from 'fs';
+import * as logSymbols from 'log-symbols';
+
+import { sh } from './sh';
+
+export async function packLocalPackage(rootDir: string, tempDir: string, packageName: string) {
+  const packagePath = path.resolve(rootDir, 'dist', 'packages', packageName.split('/')[1]);
+  const packagePathExists = !!(await fs.promises.stat(packagePath).catch(() => false));
+
+  if (!packagePathExists) {
+    throw new Error(`A directory with artifacts (${packagePath}) does not exist`);
+  }
+
+  // Use `npm pack` because `yarn pack` incorrectly calculates the included files when the
+  // files to include/exclude are specified by .npmignore rather than package.json `files`.
+  // (--quiet outputs only the .tgz filename, not all the included files)
+  const packFile = (await sh(`npm pack --quiet ${packagePath}`, tempDir, true)).trim();
+  console.log(logSymbols.success, `Package "${packageName}" was packed`);
+
+  return {
+    packageName,
+    file: packFile,
+  };
+}
diff --git a/e2e/utils/src/sh.ts b/e2e/utils/src/sh.ts
new file mode 100644
index 0000000..6b4d56d
--- /dev/null
+++ b/e2e/utils/src/sh.ts
@@ -0,0 +1,31 @@
+import * as childProcess from 'child_process';
+
+export function sh(command: string, cwd?: string, pipeOutputToResult = false): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const [cmd, ...args] = command.split(' ');
+    const options: childProcess.SpawnOptions = {
+      cwd: cwd || process.cwd(),
+      env: process.env,
+      stdio: pipeOutputToResult ? 'pipe' : 'inherit',
+      shell: true,
+    };
+
+    const child = childProcess.spawn(cmd, args, options);
+
+    let stdoutData = '';
+
+    if (child.stdout) {
+      child.stdout.on('data', data => {
+        stdoutData += data;
+      });
+    }
+
+    child.on('close', code => {
+      if (code === 0) {
+        resolve(stdoutData);
+      }
+
+      reject(new Error([`child process exited with code ${code}`, stdoutData].join('\n')));
+    });
+  });
+}
diff --git a/e2e/utils/tsconfig.json b/e2e/utils/tsconfig.json
new file mode 100644
index 0000000..96c48fe
--- /dev/null
+++ b/e2e/utils/tsconfig.json
@@ -0,0 +1,22 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "module": "CommonJS",
+    "lib": ["ES2019"],
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "files": [],
+  "include": [],
+  "references": [
+    {
+      "path": "./tsconfig.lib.json"
+    }
+  ]
+}
diff --git a/e2e/utils/tsconfig.lib.json b/e2e/utils/tsconfig.lib.json
new file mode 100644
index 0000000..94ed809
--- /dev/null
+++ b/e2e/utils/tsconfig.lib.json
@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../dist/out-tsc",
+    "declaration": true,
+    "types": ["node", "environment"]
+  },
+  "include": ["**/*.ts"]
+}
diff --git a/packages/babel-preset/src/assets/normalizeStyleRules.test.ts b/packages/babel-preset/src/assets/normalizeStyleRules.test.ts
index 11c215a..6076f61 100644
--- a/packages/babel-preset/src/assets/normalizeStyleRules.test.ts
+++ b/packages/babel-preset/src/assets/normalizeStyleRules.test.ts
@@ -128,4 +128,24 @@ describe('normalizeStyleRules', () => {
       },
     });
   });
+
+  it('handles keyframe arrays', () => {
+    expect(
+      normalizeStyleRules(
+        path.posix,
+        '/home/projects/foo',
+        '/home/projects/foo/src/styles/Component.styles.ts',
+
+        {
+          root: {
+            animationName: [{ from: { height: '20px' }, to: { height: '10px' } }],
+          },
+        },
+      ),
+    ).toEqual({
+      root: {
+        animationName: [{ from: { height: '20px' }, to: { height: '10px' } }],
+      },
+    });
+  });
 });
diff --git a/packages/babel-preset/src/transformPlugin.test.ts b/packages/babel-preset/src/transformPlugin.test.ts
index 5877d25..64c0c2b 100644
--- a/packages/babel-preset/src/transformPlugin.test.ts
+++ b/packages/babel-preset/src/transformPlugin.test.ts
@@ -53,6 +53,11 @@ pluginTester({
       fixture: path.resolve(fixturesDir, 'non-existing-module-call', 'code.ts'),
       outputFixture: path.resolve(fixturesDir, 'non-existing-module-call', 'output.ts'),
     },
+    {
+      title: 'syntax: animationName',
+      fixture: path.resolve(fixturesDir, 'keyframes', 'code.ts'),
+      outputFixture: path.resolve(fixturesDir, 'keyframes', 'output.ts'),
+    },
 
     // Assets
     //