Skip to content

Commit

Permalink
feat(core): add "classNameHashSalt" option (#586)
Browse files Browse the repository at this point in the history
* feat(core): add "classNameHashSalt" option

* Update apps/website/docs/react/api/create-dom-renderer.md

Co-authored-by: ling1726 <[email protected]>

---------

Co-authored-by: ling1726 <[email protected]>
  • Loading branch information
layershifter and ling1726 authored Jul 29, 2024
1 parent 25fc493 commit aa24116
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 2 deletions.
10 changes: 10 additions & 0 deletions apps/website/docs/react/api/create-dom-renderer.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ function App(props) {
}
```

### classNameHashSalt

A salt that will be added for generated hashed classes. Check [microsoft/griffel#453](https://github.com/microsoft/griffel/issues/453) for an example use case.

:::caution Use with caution

There cannot be more than **one single** hash salt in the same application (bundle)

:::

### compareMediaQueries

A function with the same signature as sort functions in e.g. `Array.prototype.sort` for dynamically sorting media queries. Maps over an array of media query strings.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add \"classNameHashSalt\" option",
"packageName": "@griffel/core",
"email": "[email protected]",
"dependentChangeType": "patch"
}
14 changes: 14 additions & 0 deletions packages/core/src/makeResetStyles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,18 @@ describe('makeResetStyles', () => {
}
`);
});

describe('classNameHashSalt', () => {
it('applies a salt to the hash', () => {
const rendererWithSalt = createDOMRenderer(document, { classNameHashSalt: 'salt' });

const resultWithSalt = makeResetStyles({ color: 'red' })({ dir: 'ltr', renderer });
const resultWithoutSalt = makeResetStyles({ color: 'red' })({ dir: 'ltr', renderer: rendererWithSalt });

expect(resultWithSalt).toMatchInlineSnapshot(`"rtokvmb"`);
expect(resultWithoutSalt).toMatchInlineSnapshot(`"r1fkucf3"`);

expect(resultWithSalt).not.toBe(resultWithoutSalt);
});
});
});
21 changes: 20 additions & 1 deletion packages/core/src/makeResetStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,31 @@ export function makeResetStyles(styles: GriffelResetStyle, factory: GriffelInser
let rtlClassName: string | null = null;

let cssRules: CSSRulesByBucket | string[] | null = null;
let classNameHashSalt: string;

function computeClassName(options: MakeResetStylesOptions): string {
const { dir, renderer } = options;

if (ltrClassName === null) {
[ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(styles);
[ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(styles, renderer.classNameHashSalt);

if (process.env.NODE_ENV !== 'production') {
if (renderer.classNameHashSalt) {
if (classNameHashSalt !== renderer.classNameHashSalt) {
console.error(
[
'@griffel/core:',
'\n\n',
'A provided renderer has different "classNameHashSalt".',
'This is not supported and WILL cause issues with classnames generation.',
'Ensure that all renderers created with "createDOMRenderer()" have the same "classNameHashSalt".',
].join(' '),
);
}

classNameHashSalt = renderer.classNameHashSalt;
}
}
}

insertStyles(renderer, Array.isArray(cssRules) ? { r: cssRules! } : cssRules!);
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/makeStyles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,23 @@ describe('makeStyles', () => {
`);
});

describe('classNameHashSalt', () => {
it('applies a salt to the hash', () => {
const rendererWithSalt = createDOMRenderer(document, { classNameHashSalt: 'salt' });

const computeClassesWithSalt = makeStyles({ root: { color: 'red' } });
const computeClassesWithoutSalt = makeStyles({ root: { color: 'red' } });

const resultWithSalt = computeClassesWithSalt({ dir: 'ltr', renderer }).root;
const resultWithoutSalt = computeClassesWithoutSalt({ dir: 'ltr', renderer: rendererWithSalt }).root;

expect(resultWithSalt).toMatchInlineSnapshot(`"___afhpfp0 fe3e8s9"`);
expect(resultWithoutSalt).toMatchInlineSnapshot(`"___eoxc7a0 fl2dfm4"`);

expect(resultWithSalt).not.toBe(resultWithoutSalt);
});
});

it.each<'test' | 'development'>(['test', 'development'])(
'in non-production mode, hashes include debug information',
env => {
Expand Down
22 changes: 21 additions & 1 deletion packages/core/src/makeStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,31 @@ export function makeStyles<Slots extends string | number>(
sourceURL = getSourceURLfromError();
}

let classNameHashSalt: string;

function computeClasses(options: MakeStylesOptions): Record<Slots, string> {
const { dir, renderer } = options;

if (classesMapBySlot === null) {
[classesMapBySlot, cssRules] = resolveStyleRulesForSlots(stylesBySlots);
[classesMapBySlot, cssRules] = resolveStyleRulesForSlots(stylesBySlots, renderer.classNameHashSalt);

if (process.env.NODE_ENV !== 'production') {
if (renderer.classNameHashSalt) {
if (classNameHashSalt !== renderer.classNameHashSalt) {
console.error(
[
'@griffel/core:',
'\n\n',
'A provided renderer has different "classNameHashSalt".',
'This is not supported and WILL cause issues with classnames generation.',
'Ensure that all renderers created with "createDOMRenderer()" have the same "classNameHashSalt".',
].join(' '),
);
}

classNameHashSalt = renderer.classNameHashSalt;
}
}
}

const isLTR = dir === 'ltr';
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/renderer/createDOMRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { safeInsertRule } from './safeInsertRule';
let lastIndex = 0;

export interface CreateDOMRendererOptions {
/**
* A salt that will be added for hashed classes. Should be the same for all renderers in the same application
* (bundle).
*
* @see https://github.com/microsoft/griffel/issues/453
*/
classNameHashSalt?: string;

/**
* If specified, a renderer will insert created style tags after this element.
*/
Expand Down Expand Up @@ -50,12 +58,14 @@ export function createDOMRenderer(
options: CreateDOMRendererOptions = {},
): GriffelRenderer {
const {
classNameHashSalt,
unstable_filterCSSRule,
insertionPoint,
styleElementAttributes,
compareMediaQueries = defaultCompareMediaQueries,
} = options;
const renderer: GriffelRenderer = {
classNameHashSalt,
insertionCache: {},
stylesheets: {},
styleElementAttributes: Object.freeze(styleElementAttributes),
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export interface IsomorphicStyleSheet {
export interface GriffelRenderer {
id: string;

/**
* @private
*/
classNameHashSalt?: string;

/**
* @private
*/
Expand Down

0 comments on commit aa24116

Please sign in to comment.