Repository: microsoft/griffel
Pull request #323 (2023-01-26 10:52:10)

feat: implement "unstable_keepOriginalCode" for extraction plugin
This PR adds `unstable_keepOriginalCode` to Webpack loader options in `@griffel/webpack-extraction-plugin`.

⚠️ **Please never use this option, it will be remove without further notice.**

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

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

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index 68a7ae4..0e629bb 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -39,7 +39,8 @@
       "files": ["*.ts", "*.tsx"],
       "extends": ["plugin:@nrwl/nx/typescript"],
       "rules": {
-        "@typescript-eslint/no-non-null-assertion": "off"
+        "@typescript-eslint/no-non-null-assertion": "off",
+        "@typescript-eslint/no-unused-vars": "error"
       }
     },
     {
diff --git a/change/@griffel-devtools-240ea0de-0d32-481f-8573-315857bcf791.json b/change/@griffel-devtools-240ea0de-0d32-481f-8573-315857bcf791.json
new file mode 100644
index 0000000..c6a0ac8
--- /dev/null
+++ b/change/@griffel-devtools-240ea0de-0d32-481f-8573-315857bcf791.json
@@ -0,0 +1,7 @@
+{
+  "type": "none",
+  "comment": "chore: remove unused import",
+  "packageName": "@griffel/devtools",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "none"
+}
diff --git a/change/@griffel-webpack-extraction-plugin-1c5a4879-b2c1-43e5-af5e-7cca09556b45.json b/change/@griffel-webpack-extraction-plugin-1c5a4879-b2c1-43e5-af5e-7cca09556b45.json
new file mode 100644
index 0000000..9269d09
--- /dev/null
+++ b/change/@griffel-webpack-extraction-plugin-1c5a4879-b2c1-43e5-af5e-7cca09556b45.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "feat: implement \"unstable_keepOriginalCode\" for extraction plugin",
+  "packageName": "@griffel/webpack-extraction-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-webpack-extraction-plugin-84f522e8-cf92-477d-a545-572c97b0ab48.json b/change/@griffel-webpack-extraction-plugin-84f522e8-cf92-477d-a545-572c97b0ab48.json
new file mode 100644
index 0000000..d9e8f7d
--- /dev/null
+++ b/change/@griffel-webpack-extraction-plugin-84f522e8-cf92-477d-a545-572c97b0ab48.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: properly handle @media in __resetStyles()",
+  "packageName": "@griffel/webpack-extraction-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/devtools/src/FlattenView.tsx b/packages/devtools/src/FlattenView.tsx
index 6c08c56..20113a4 100644
--- a/packages/devtools/src/FlattenView.tsx
+++ b/packages/devtools/src/FlattenView.tsx
@@ -1,5 +1,4 @@
 import * as React from 'react';
-import hash from '@emotion/hash';
 import { makeStaticStyles, makeStyles, shorthands } from '@griffel/react';
 
 import { SlotCSSRules } from './SlotCSSRules';
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/code.ts
new file mode 100644
index 0000000..30545ef
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/code.ts
@@ -0,0 +1,5 @@
+import { __resetStyles } from '@griffel/react';
+export const useClassName = __resetStyles('r13wlxb8', null, [
+  '.r13wlxb8{line-height:0;}',
+  '@media screen and (prefers-reduced-motion: reduce){.r13wlxb8{transition-duration:0.01ms;}}',
+]);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/output.css
new file mode 100644
index 0000000..20c9f1e
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/reset-media/output.css
@@ -0,0 +1,9 @@
+/** griffel.css **/
+.r13wlxb8 {
+  line-height: 0;
+}
+@media screen and (prefers-reduced-motion: reduce) {
+  .r13wlxb8 {
+    transition-duration: 0.01ms;
+  }
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/code.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/code.ts
new file mode 100644
index 0000000..65a7c27
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/code.ts
@@ -0,0 +1,17 @@
+import { __resetStyles, __styles } from '@griffel/react';
+
+export const useClasses = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+
+export const useClassName = __resetStyles('rjefjbm', 'r7z97ji', [
+  '.rjefjbm{color:red;padding-left:4px;}',
+  '.r7z97ji{color:red;padding-right:4px;}',
+]);
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/fs.json b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/fs.json
new file mode 100644
index 0000000..966fca3
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/fs.json
@@ -0,0 +1,1 @@
+["bundle.js", "griffel.css"]
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.css b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.css
new file mode 100644
index 0000000..53761cb
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.css
@@ -0,0 +1,12 @@
+/** griffel.css **/
+.rjefjbm {
+  color: red;
+  padding-left: 4px;
+}
+.r7z97ji {
+  color: red;
+  padding-right: 4px;
+}
+.fe3e8s9 {
+  color: red;
+}
diff --git a/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.ts b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.ts
new file mode 100644
index 0000000..d87a05d
--- /dev/null
+++ b/packages/webpack-extraction-plugin/__fixtures__/webpack/unstable-keep-original-code/output.ts
@@ -0,0 +1,17 @@
+import { __resetStyles, __styles } from '@griffel/react';
+export const useClasses = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+export const useClassName = __resetStyles('rjefjbm', 'r7z97ji', [
+  '.rjefjbm{color:red;padding-left:4px;}',
+  '.r7z97ji{color:red;padding-right:4px;}',
+]);
+
+require('./code.griffel.css!=!../../../virtual-loader/index.js?style=%2F**%20%40griffel%3Acss-start%20%5Br%5D%20**%2F%0A.rjefjbm%7Bcolor%3Ared%3Bpadding-left%3A4px%3B%7D.r7z97ji%7Bcolor%3Ared%3Bpadding-right%3A4px%3B%7D%0A%2F**%20%40griffel%3Acss-end%20**%2F%0A%2F**%20%40griffel%3Acss-start%20%5Bd%5D%20**%2F%0A.fe3e8s9%7Bcolor%3Ared%3B%7D%0A%2F**%20%40griffel%3Acss-end%20**%2F%0A!./code.ts');
diff --git a/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts
index c27e954..0bbc607 100644
--- a/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts
+++ b/packages/webpack-extraction-plugin/src/babelPluginStripGriffelRuntime.ts
@@ -3,7 +3,9 @@ import { addNamed } from '@babel/helper-module-imports';
 import { declare } from '@babel/helper-plugin-utils';
 import type { CSSRulesByBucket } from '@griffel/core';
 
-type StripRuntimeBabelPluginOptions = Record<string, unknown>;
+type StripRuntimeBabelPluginOptions = {
+  unstable_keepOriginalCode?: boolean;
+};
 
 type FunctionKinds = '__styles' | '__resetStyles';
 
@@ -29,6 +31,7 @@ function evaluateAndUpdateArgument(
   argumentPath: NodePath<t.ObjectExpression> | NodePath<t.ArrayExpression>,
   functionKind: FunctionKinds,
   state: StripRuntimeBabelPluginState,
+  unstable_keepOriginalCode?: boolean,
 ) {
   // Returns the styles as a JavaScript object
   const evaluationResult = argumentPath.evaluate();
@@ -52,7 +55,9 @@ function evaluateAndUpdateArgument(
     state.cssRulesByBucket = concatCSSRulesByBucket(state.cssRulesByBucket, { r: cssRules });
   }
 
-  argumentPath.remove();
+  if (!unstable_keepOriginalCode) {
+    argumentPath.remove();
+  }
 }
 
 function getFunctionArgumentPath(
@@ -154,9 +159,12 @@ function updateReferences(
   importSpecifierPath: NodePath<t.ImportSpecifier>,
   importSource: string,
   functionKind: FunctionKinds,
+  unstable_keepOriginalCode?: boolean,
 ) {
   const importName = functionKind === '__styles' ? '__css' : '__resetCSS';
-  const importIdentifier = addNamed(importSpecifierPath, importName, importSource);
+  const importIdentifier = unstable_keepOriginalCode
+    ? undefined
+    : addNamed(importSpecifierPath, importName, importSource);
 
   const referencePaths = getReferencePaths(importSpecifierPath, functionKind);
 
@@ -167,9 +175,11 @@ function updateReferences(
 
       if (argumentPath) {
         inlineAssetImports(argumentPath);
-        evaluateAndUpdateArgument(argumentPath, functionKind, state);
+        evaluateAndUpdateArgument(argumentPath, functionKind, state, unstable_keepOriginalCode);
 
-        updateCalleeName(callExpressionPath, importIdentifier.name);
+        if (importIdentifier) {
+          updateCalleeName(callExpressionPath, importIdentifier.name);
+        }
       }
     }
   }
@@ -178,7 +188,7 @@ function updateReferences(
 export const babelPluginStripGriffelRuntime = declare<
   Partial<StripRuntimeBabelPluginOptions>,
   PluginObj<StripRuntimeBabelPluginState>
->(api => {
+>((api, options) => {
   api.assertVersion(7);
 
   return {
@@ -211,9 +221,9 @@ export const babelPluginStripGriffelRuntime = declare<
               const importSource = importSourcePath.node.value;
 
               if (importedPath.isIdentifier({ name: '__styles' })) {
-                updateReferences(state, path, importSource, '__styles');
+                updateReferences(state, path, importSource, '__styles', options.unstable_keepOriginalCode);
               } else if (importedPath.isIdentifier({ name: '__resetStyles' })) {
-                updateReferences(state, path, importSource, '__resetStyles');
+                updateReferences(state, path, importSource, '__resetStyles', options.unstable_keepOriginalCode);
               }
             },
           });
diff --git a/packages/webpack-extraction-plugin/src/parseCSSRules.ts b/packages/webpack-extraction-plugin/src/parseCSSRules.ts
index fa07e96..1cb92a8 100644
--- a/packages/webpack-extraction-plugin/src/parseCSSRules.ts
+++ b/packages/webpack-extraction-plugin/src/parseCSSRules.ts
@@ -1,5 +1,5 @@
 import { styleBucketOrdering } from '@griffel/core';
-import { COMMENT, compile, MEDIA, serialize, stringify } from 'stylis';
+import { COMMENT, compile, serialize, stringify } from 'stylis';
 
 import type { CSSBucketEntry, CSSRulesByBucket, StyleBucketName } from '@griffel/core';
 
@@ -37,7 +37,7 @@ export function parseCSSRules(css: string) {
 
     if (cssBucketName) {
       const cssRule = serialize([element], stringify);
-      const bucketEntry: CSSBucketEntry = element.type === MEDIA ? [cssRule, cssMeta!] : cssRule;
+      const bucketEntry: CSSBucketEntry = cssBucketName === 'm' ? [cssRule, cssMeta!] : cssRule;
 
       cssRulesByBucket[cssBucketName].push(bucketEntry);
       continue;
diff --git a/packages/webpack-extraction-plugin/src/sortCSSRules.ts b/packages/webpack-extraction-plugin/src/sortCSSRules.ts
index a990f0d..8fd13f7 100644
--- a/packages/webpack-extraction-plugin/src/sortCSSRules.ts
+++ b/packages/webpack-extraction-plugin/src/sortCSSRules.ts
@@ -12,7 +12,7 @@ function getCSSMetaFromBucketEntry(entry: CSSBucketEntry): Record<string, unknow
 export function sortCSSRules(
   setOfCSSRules: CSSRulesByBucket[],
   compareMediaQueries: GriffelRenderer['compareMediaQueries'],
-) {
+): string {
   return styleBucketOrdering
     .map(styleBucketName => {
       return {
diff --git a/packages/webpack-extraction-plugin/src/transformSync.ts b/packages/webpack-extraction-plugin/src/transformSync.ts
index 4dcd316..2007d49 100644
--- a/packages/webpack-extraction-plugin/src/transformSync.ts
+++ b/packages/webpack-extraction-plugin/src/transformSync.ts
@@ -8,6 +8,8 @@ export type TransformOptions = {
 
   inputSourceMap: Babel.TransformOptions['inputSourceMap'];
   enableSourceMaps: boolean;
+
+  unstable_keepOriginalCode?: boolean;
 };
 
 export type TransformResult = {
@@ -38,7 +40,7 @@ export function transformSync(sourceCode: string, options: TransformOptions): Tr
     // Ignore all user's configs and apply only our plugin
     babelrc: false,
     configFile: false,
-    plugins: [babelPluginStripGriffelRuntime],
+    plugins: [[babelPluginStripGriffelRuntime, { unstable_keepOriginalCode: options.unstable_keepOriginalCode }]],
 
     filename: options.filename,
 
diff --git a/packages/webpack-extraction-plugin/src/webpackLoader.ts b/packages/webpack-extraction-plugin/src/webpackLoader.ts
index 131432e..ca3f368 100644
--- a/packages/webpack-extraction-plugin/src/webpackLoader.ts
+++ b/packages/webpack-extraction-plugin/src/webpackLoader.ts
@@ -4,7 +4,12 @@ import * as webpack from 'webpack';
 
 import { transformSync, TransformResult, TransformOptions } from './transformSync';
 
-type WebpackLoaderOptions = never;
+export type WebpackLoaderOptions = {
+  /**
+   * Please never use this feature, it will be removed without further notice.
+   */
+  unstable_keepOriginalCode?: boolean;
+};
 
 type WebpackLoaderParams = Parameters<webpack.LoaderDefinitionFunction<WebpackLoaderOptions>>;
 
@@ -46,6 +51,8 @@ function webpackLoader(
     return;
   }
 
+  const { unstable_keepOriginalCode } = this.getOptions();
+
   let result: TransformResult | null = null;
   let error: Error | null = null;
 
@@ -55,6 +62,8 @@ function webpackLoader(
 
       enableSourceMaps: this.sourceMap || false,
       inputSourceMap: parseSourceMap(inputSourceMap),
+
+      unstable_keepOriginalCode,
     });
   } catch (err) {
     error = err as Error;


Test patch:
diff --git a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
index aba0028..43825c7 100644
--- a/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
+++ b/packages/webpack-extraction-plugin/src/GriffelCSSExtractionPlugin.test.ts
@@ -7,9 +7,11 @@ import * as webpack from 'webpack';
 import { merge } from 'webpack-merge';
 
 import { GriffelCSSExtractionPlugin } from './GriffelCSSExtractionPlugin';
+import { WebpackLoaderOptions } from './webpackLoader';
 
 type CompileOptions = {
   cssFilename?: string;
+  loaderOptions?: WebpackLoaderOptions;
   webpackConfig?: webpack.Configuration;
 };
 
@@ -47,6 +49,7 @@ async function compileSourceWithWebpack(
           include: path.dirname(entryPath),
           use: {
             loader: GriffelCSSExtractionPlugin.loader,
+            options: options.loaderOptions,
           },
         },
         {
@@ -212,6 +215,7 @@ describe('webpackLoader', () => {
   // Basic assertions
   testFixture('basic-rules');
   testFixture('reset');
+  testFixture('reset-media');
   testFixture('mixed');
 
   // Multiple calls of __styles
@@ -239,4 +243,7 @@ describe('webpackLoader', () => {
 
   // Chunks
   testFixture('with-chunks');
+
+  // Unstable
+  testFixture('unstable-keep-original-code', { loaderOptions: { unstable_keepOriginalCode: true } });
 });
diff --git a/packages/webpack-extraction-plugin/src/webpackLoader.test.ts b/packages/webpack-extraction-plugin/src/webpackLoader.test.ts
index 8201d1a..78c4295 100644
--- a/packages/webpack-extraction-plugin/src/webpackLoader.test.ts
+++ b/packages/webpack-extraction-plugin/src/webpackLoader.test.ts
@@ -5,7 +5,10 @@ import * as prettier from 'prettier';
 import * as webpack from 'webpack';
 import { merge } from 'webpack-merge';
 
+import type { WebpackLoaderOptions } from './webpackLoader';
+
 type CompileOptions = {
+  loaderOptions?: WebpackLoaderOptions;
   webpackConfig?: webpack.Configuration;
 };
 
@@ -36,6 +39,7 @@ async function compileSourceWithWebpack(entryPath: string, options: CompileOptio
           include: path.dirname(entryPath),
           use: {
             loader: path.resolve(__dirname, './webpackLoader.ts'),
+            options: options.loaderOptions,
           },
         },
         {
@@ -193,4 +197,7 @@ describe('webpackLoader', () => {
 
   // Ensures that a file without __styles calls remains unprocessed
   testFixture('missing-calls');
+
+  // Unstable
+  testFixture('unstable-keep-original-code', { loaderOptions: { unstable_keepOriginalCode: true } });
 });