From 888948616984a18b960a5877606870bdfae4009a Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Wed, 17 Apr 2024 15:12:37 +0200 Subject: [PATCH] feat: add "classNameHashSalt" for AOT --- .../config-classname-hash-salt/code.ts | 5 +++ .../config-classname-hash-salt/output.ts | 12 +++++++ packages/babel-preset/src/schema.ts | 3 ++ .../babel-preset/src/transformPlugin.test.ts | 8 +++++ packages/babel-preset/src/transformPlugin.ts | 3 ++ packages/babel-preset/src/types.ts | 2 ++ .../core/src/resolveStyleRulesForSlots.ts | 4 ++- .../src/runtime/resolveResetStyleRules.ts | 5 +-- .../core/src/runtime/resolveStyleRules.ts | 16 ++++++++-- .../src/runtime/utils/hashClassName.test.ts | 31 +++++++++++++++++++ .../core/src/runtime/utils/hashClassName.ts | 6 ++-- packages/vite-plugin/project.json | 1 + .../config-classname-hash-salt/code.ts | 5 +++ .../config-classname-hash-salt/output.ts | 12 +++++++ .../webpack-loader/src/webpackLoader.test.ts | 6 ++++ 15 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 packages/babel-preset/__fixtures__/config-classname-hash-salt/code.ts create mode 100644 packages/babel-preset/__fixtures__/config-classname-hash-salt/output.ts create mode 100644 packages/core/src/runtime/utils/hashClassName.test.ts create mode 100644 packages/webpack-loader/__fixtures__/config-classname-hash-salt/code.ts create mode 100644 packages/webpack-loader/__fixtures__/config-classname-hash-salt/output.ts diff --git a/packages/babel-preset/__fixtures__/config-classname-hash-salt/code.ts b/packages/babel-preset/__fixtures__/config-classname-hash-salt/code.ts new file mode 100644 index 000000000..64eef52c9 --- /dev/null +++ b/packages/babel-preset/__fixtures__/config-classname-hash-salt/code.ts @@ -0,0 +1,5 @@ +import { makeStyles } from '@griffel/react'; + +export const useStyles = makeStyles({ + root: { color: 'red', paddingLeft: '4px' }, +}); diff --git a/packages/babel-preset/__fixtures__/config-classname-hash-salt/output.ts b/packages/babel-preset/__fixtures__/config-classname-hash-salt/output.ts new file mode 100644 index 000000000..34444ce6e --- /dev/null +++ b/packages/babel-preset/__fixtures__/config-classname-hash-salt/output.ts @@ -0,0 +1,12 @@ +import { __styles } from '@griffel/react'; +export const useStyles = __styles( + { + root: { + sj55zd: 'feohi3x', + uwmqm3: ['f1rwgqon', 'f1tyzn0d'], + }, + }, + { + d: ['.feohi3x{color:red;}', '.f1rwgqon{padding-left:4px;}', '.f1tyzn0d{padding-right:4px;}'], + }, +); diff --git a/packages/babel-preset/src/schema.ts b/packages/babel-preset/src/schema.ts index 71d1ae8f8..d3ee43e67 100644 --- a/packages/babel-preset/src/schema.ts +++ b/packages/babel-preset/src/schema.ts @@ -6,6 +6,9 @@ export const configSchema: JSONSchema7 = { type: 'object', properties: { + classNameHashSalt: { + type: 'string', + }, generateMetadata: { type: 'boolean', }, diff --git a/packages/babel-preset/src/transformPlugin.test.ts b/packages/babel-preset/src/transformPlugin.test.ts index bcf7c8404..460a1d4d5 100644 --- a/packages/babel-preset/src/transformPlugin.test.ts +++ b/packages/babel-preset/src/transformPlugin.test.ts @@ -157,6 +157,14 @@ pluginTester({ // Configs // // + { + title: 'config: classNameHashSalt', + fixture: path.resolve(fixturesDir, 'config-classname-hash-salt', 'code.ts'), + outputFixture: path.resolve(fixturesDir, 'config-classname-hash-salt', 'output.ts'), + pluginOptions: { + classNameHashSalt: 'prefix', + }, + }, { title: 'config: babelOptions', fixture: path.resolve(fixturesDir, 'config-babel-options', 'code.ts'), diff --git a/packages/babel-preset/src/transformPlugin.ts b/packages/babel-preset/src/transformPlugin.ts index 34cd32ace..95b8d6446 100644 --- a/packages/babel-preset/src/transformPlugin.ts +++ b/packages/babel-preset/src/transformPlugin.ts @@ -200,6 +200,7 @@ export const transformPlugin = declare, PluginObj = { babelOptions: {}, + classNameHashSalt: '', generateMetadata: false, modules: [ { moduleSource: '@griffel/react', importName: 'makeStyles' }, @@ -296,6 +297,7 @@ export const transformPlugin = declare, PluginObj, PluginObj( stylesBySlots: StylesBySlots, + classNameHashSalt: string = '', ): [CSSClassesMapBySlot, CSSRulesByBucket] { const classesMapBySlot = {} as CSSClassesMapBySlot; const cssRules: CSSRulesByBucket = {}; @@ -19,7 +21,7 @@ export function resolveStyleRulesForSlots( // eslint-disable-next-line guard-for-in for (const slotName in stylesBySlots) { const slotStyles: GriffelStyle = stylesBySlots[slotName]; - const [cssClassMap, cssRulesByBucket] = resolveStyleRules(slotStyles); + const [cssClassMap, cssRulesByBucket] = resolveStyleRules(slotStyles, classNameHashSalt); classesMapBySlot[slotName] = cssClassMap; diff --git a/packages/core/src/runtime/resolveResetStyleRules.ts b/packages/core/src/runtime/resolveResetStyleRules.ts index 9e45ded7e..f6add60ca 100644 --- a/packages/core/src/runtime/resolveResetStyleRules.ts +++ b/packages/core/src/runtime/resolveResetStyleRules.ts @@ -138,10 +138,11 @@ function createStringFromStyles(styles: GriffelResetStyle) { */ export function resolveResetStyleRules( styles: GriffelResetStyle, + classNameHashSalt: string = '', ): [string, string | null, CSSRulesByBucket | string[]] { const [ltrRule, rtlRule] = createStringFromStyles(styles); - const ltrClassName = RESET_HASH_PREFIX + hashString(ltrRule); + const ltrClassName = RESET_HASH_PREFIX + hashString(classNameHashSalt + ltrRule); const [ltrCSS, ltrCSSAtRules] = compileResetCSSRules(`.${ltrClassName}{${ltrRule}}`); const hasAtRules = ltrCSSAtRules.length > 0; @@ -150,7 +151,7 @@ export function resolveResetStyleRules( return [ltrClassName, null, hasAtRules ? { r: ltrCSS, s: ltrCSSAtRules } : ltrCSS]; } - const rtlClassName = RESET_HASH_PREFIX + hashString(rtlRule); + const rtlClassName = RESET_HASH_PREFIX + hashString(classNameHashSalt + rtlRule); const [rtlCSS, rtlCSSAtRules] = compileResetCSSRules(`.${rtlClassName}{${rtlRule}}`); return [ diff --git a/packages/core/src/runtime/resolveStyleRules.ts b/packages/core/src/runtime/resolveStyleRules.ts index 47f2f582e..9c00fc9dd 100644 --- a/packages/core/src/runtime/resolveStyleRules.ts +++ b/packages/core/src/runtime/resolveStyleRules.ts @@ -86,6 +86,7 @@ function pushToCSSRules( */ export function resolveStyleRules( styles: GriffelStyle, + classNameHashSalt: string = '', selectors: string[] = [], atRules: AtRules = { container: '', @@ -129,7 +130,7 @@ export function resolveStyleRules( const shorthandProperties = shorthand[1]; const shorthandResetStyles = Object.fromEntries(shorthandProperties.map(property => [property, RESET])); - resolveStyleRules(shorthandResetStyles, selectors, atRules, cssClassesMap, cssRulesByBucket); + resolveStyleRules(shorthandResetStyles, classNameHashSalt, selectors, atRules, cssClassesMap, cssRulesByBucket); } // uniq key based on a hash of property & selector, used for merging later @@ -137,6 +138,7 @@ export function resolveStyleRules( const className = hashClassName( { value: value.toString(), + salt: classNameHashSalt, selector, property, }, @@ -151,6 +153,7 @@ export function resolveStyleRules( { value: rtlDefinition.value.toString(), property: rtlDefinition.key, + salt: classNameHashSalt, selector, }, atRules, @@ -228,6 +231,7 @@ export function resolveStyleRules( resolveStyleRules( { animationName: animationNames.join(', ') }, + classNameHashSalt, selectors, atRules, cssClassesMap, @@ -252,13 +256,14 @@ export function resolveStyleRules( const shorthandProperties = shorthand[1]; const shorthandResetStyles = Object.fromEntries(shorthandProperties.map(property => [property, RESET])); - resolveStyleRules(shorthandResetStyles, selectors, atRules, cssClassesMap, cssRulesByBucket); + resolveStyleRules(shorthandResetStyles, classNameHashSalt, selectors, atRules, cssClassesMap, cssRulesByBucket); } const key = hashPropertyKey(selector, property, atRules); const className = hashClassName( { value: value.map(v => (v ?? '').toString()).join(';'), + salt: classNameHashSalt, selector, property, }, @@ -266,7 +271,6 @@ export function resolveStyleRules( ); const rtlDefinitions = value.map(v => convertProperty(property, v!)); - const rtlPropertyConsistent = !rtlDefinitions.some(v => v.key !== rtlDefinitions[0].key); if (!rtlPropertyConsistent) { @@ -284,6 +288,7 @@ export function resolveStyleRules( ? hashClassName( { value: rtlDefinitions.map(v => (v?.value ?? '').toString()).join(';'), + salt: classNameHashSalt, property: rtlDefinitions[0].key, selector, }, @@ -324,6 +329,7 @@ export function resolveStyleRules( if (isNestedSelector(property)) { resolveStyleRules( value as GriffelStyle, + classNameHashSalt, selectors.concat(normalizeNestedProperty(property)), atRules, cssClassesMap, @@ -334,6 +340,7 @@ export function resolveStyleRules( resolveStyleRules( value as GriffelStyle, + classNameHashSalt, selectors, { ...atRules, media: combinedMediaQuery }, cssClassesMap, @@ -344,6 +351,7 @@ export function resolveStyleRules( resolveStyleRules( value as GriffelStyle, + classNameHashSalt, selectors, { ...atRules, layer: combinedLayerQuery }, cssClassesMap, @@ -354,6 +362,7 @@ export function resolveStyleRules( resolveStyleRules( value as GriffelStyle, + classNameHashSalt, selectors, { ...atRules, supports: combinedSupportQuery }, cssClassesMap, @@ -367,6 +376,7 @@ export function resolveStyleRules( resolveStyleRules( value as GriffelStyle, + classNameHashSalt, selectors, { ...atRules, container: containerQuery }, cssClassesMap, diff --git a/packages/core/src/runtime/utils/hashClassName.test.ts b/packages/core/src/runtime/utils/hashClassName.test.ts new file mode 100644 index 000000000..1f9fe6da6 --- /dev/null +++ b/packages/core/src/runtime/utils/hashClassName.test.ts @@ -0,0 +1,31 @@ +import { hashClassName } from './hashClassName'; + +const defaultOptions = { + property: 'color', + selector: '', + value: 'red', + + salt: '', +}; +const defaultAtRules = { + container: '', + media: '', + layer: '', + supports: '', +}; + +describe('hashClassName', () => { + it('should hash the className', () => { + expect(hashClassName(defaultOptions, defaultAtRules)).toMatchInlineSnapshot(`"fe3e8s9"`); + }); + + it('should use salt for hash', () => { + const withoutSalt = hashClassName(defaultOptions, defaultAtRules); + const withSalt = hashClassName({ ...defaultOptions, salt: 'HASH_SALT' }, defaultAtRules); + + expect(withoutSalt).not.toEqual(withSalt); + + expect(withoutSalt).toMatchInlineSnapshot(`"fe3e8s9"`); + expect(withSalt).toMatchInlineSnapshot(`"f3mwu0g"`); + }); +}); diff --git a/packages/core/src/runtime/utils/hashClassName.ts b/packages/core/src/runtime/utils/hashClassName.ts index af852e4d3..302385d0a 100644 --- a/packages/core/src/runtime/utils/hashClassName.ts +++ b/packages/core/src/runtime/utils/hashClassName.ts @@ -6,14 +6,16 @@ import type { AtRules } from './types'; interface HashedClassNameParts { property: string; value: string; + salt: string; selector: string; } -export function hashClassName({ property, selector, value }: HashedClassNameParts, atRules: AtRules): string { +export function hashClassName({ property, selector, salt, value }: HashedClassNameParts, atRules: AtRules): string { return ( HASH_PREFIX + hashString( - selector + + salt + + selector + atRules.container + atRules.media + atRules.layer + diff --git a/packages/vite-plugin/project.json b/packages/vite-plugin/project.json index 0599b48d6..4cd91a816 100644 --- a/packages/vite-plugin/project.json +++ b/packages/vite-plugin/project.json @@ -10,6 +10,7 @@ }, "test": { "executor": "nx:run-commands", + "dependsOn": [{ "target": "build", "dependencies": true }], "options": { "cwd": "packages/vite-plugin", "commands": [{ "command": "node --test **/*.test.mjs" }], diff --git a/packages/webpack-loader/__fixtures__/config-classname-hash-salt/code.ts b/packages/webpack-loader/__fixtures__/config-classname-hash-salt/code.ts new file mode 100644 index 000000000..87e16f368 --- /dev/null +++ b/packages/webpack-loader/__fixtures__/config-classname-hash-salt/code.ts @@ -0,0 +1,5 @@ +import { makeStyles } from '@griffel/react'; + +export const styles = makeStyles({ + root: { color: 'red', paddingLeft: '10px' }, +}); diff --git a/packages/webpack-loader/__fixtures__/config-classname-hash-salt/output.ts b/packages/webpack-loader/__fixtures__/config-classname-hash-salt/output.ts new file mode 100644 index 000000000..16636cc92 --- /dev/null +++ b/packages/webpack-loader/__fixtures__/config-classname-hash-salt/output.ts @@ -0,0 +1,12 @@ +import { __styles } from '@griffel/react'; +export const styles = __styles( + { + root: { + sj55zd: 'f3mwu0g', + uwmqm3: ['f1rfztu6', 'f1h66kgv'], + }, + }, + { + d: ['.f3mwu0g{color:red;}', '.f1rfztu6{padding-left:10px;}', '.f1h66kgv{padding-right:10px;}'], + }, +); diff --git a/packages/webpack-loader/src/webpackLoader.test.ts b/packages/webpack-loader/src/webpackLoader.test.ts index 4dc73fa41..409b20330 100644 --- a/packages/webpack-loader/src/webpackLoader.test.ts +++ b/packages/webpack-loader/src/webpackLoader.test.ts @@ -235,6 +235,12 @@ describe('webpackLoader', () => { testFixture('empty'); // Integration fixtures for config functionality + testFixture('config-classname-hash-salt', { + loaderOptions: { + classNameHashSalt: 'HASH_SALT', + }, + }); + testFixture('config-modules', { loaderOptions: { modules: [{ moduleSource: 'react-make-styles', importName: 'makeStyles' }],