Skip to content

Commit 9f7710e

Browse files
authored
feat: insertionFactory API for @griffel/core (#407)
* feat: insertionFactory API * add tests
1 parent 9e29013 commit 9f7710e

11 files changed

+120
-57
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "feat: add API for styles insertion",
4+
"packageName": "@griffel/core",
5+
"email": "olfedias@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/core/src/__resetStyles.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import { DEBUG_RESET_CLASSES } from './constants';
2+
import { insertionFactory } from './insertionFactory';
23
import type { MakeResetStylesOptions } from './makeResetStyles';
4+
import type { GriffelInsertionFactory } from './types';
35

46
/**
57
* @internal
68
*/
7-
export function __resetStyles(ltrClassName: string, rtlClassName: string | null, cssRules: string[]) {
8-
const insertionCache: Record<string, boolean> = {};
9+
export function __resetStyles(
10+
ltrClassName: string,
11+
rtlClassName: string | null,
12+
cssRules: string[],
13+
factory: GriffelInsertionFactory = insertionFactory,
14+
) {
15+
const insertStyles = factory();
916

1017
function computeClassName(options: MakeResetStylesOptions): string {
1118
const { dir, renderer } = options;
19+
const className = dir === 'ltr' ? ltrClassName : rtlClassName || ltrClassName;
1220

13-
const isLTR = dir === 'ltr';
14-
// As RTL classes are different they should have a different cache key for insertion
15-
const rendererId = isLTR ? renderer.id : renderer.id + 'r';
16-
17-
if (insertionCache[rendererId] === undefined) {
18-
renderer.insertCSSRules({ r: cssRules! });
19-
insertionCache[rendererId] = true;
20-
}
21-
22-
const className = isLTR ? ltrClassName : rtlClassName || ltrClassName;
21+
insertStyles(renderer, { r: cssRules });
2322

2423
if (process.env.NODE_ENV !== 'production') {
2524
DEBUG_RESET_CLASSES[className] = 1;

packages/core/src/__styles.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { debugData, isDevToolsEnabled, getSourceURLfromError } from './devtools';
2+
import { insertionFactory } from './insertionFactory';
23
import { reduceToClassNameForSlots } from './runtime/reduceToClassNameForSlots';
3-
import type { CSSClassesMapBySlot, CSSRulesByBucket } from './types';
4+
import type { CSSClassesMapBySlot, CSSRulesByBucket, GriffelInsertionFactory } from './types';
45
import type { MakeStylesOptions } from './makeStyles';
56

67
/**
@@ -11,8 +12,9 @@ import type { MakeStylesOptions } from './makeStyles';
1112
export function __styles<Slots extends string>(
1213
classesMapBySlot: CSSClassesMapBySlot<Slots>,
1314
cssRules: CSSRulesByBucket,
15+
factory: GriffelInsertionFactory = insertionFactory,
1416
) {
15-
const insertionCache: Record<string, boolean> = {};
17+
const insertStyles = factory();
1618

1719
let ltrClassNamesForSlots: Record<Slots, string> | null = null;
1820
let rtlClassNamesForSlots: Record<Slots, string> | null = null;
@@ -24,10 +26,7 @@ export function __styles<Slots extends string>(
2426

2527
function computeClasses(options: Pick<MakeStylesOptions, 'dir' | 'renderer'>): Record<Slots, string> {
2628
const { dir, renderer } = options;
27-
2829
const isLTR = dir === 'ltr';
29-
// As RTL classes are different they should have a different cache key for insertion
30-
const rendererId = isLTR ? renderer.id : renderer.id + 'r';
3130

3231
if (isLTR) {
3332
if (ltrClassNamesForSlots === null) {
@@ -39,10 +38,7 @@ export function __styles<Slots extends string>(
3938
}
4039
}
4140

42-
if (insertionCache[rendererId] === undefined) {
43-
renderer.insertCSSRules(cssRules!);
44-
insertionCache[rendererId] = true;
45-
}
41+
insertStyles(renderer, cssRules);
4642

4743
const classNamesForSlots = isLTR
4844
? (ltrClassNamesForSlots as Record<Slots, string>)

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export {
102102
StyleBucketName,
103103
// Util
104104
GriffelRenderer,
105+
GriffelInsertionFactory,
105106
} from './types';
106107

107108
// Private exports, are used by devtools
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { insertionFactory } from './insertionFactory';
2+
import type { GriffelRenderer } from './types';
3+
4+
describe('insertionFactory', () => {
5+
it('should return a function', () => {
6+
expect(insertionFactory()).toBeInstanceOf(Function);
7+
});
8+
9+
it('inserts CSS rules only once per renderer', () => {
10+
const rendererA: Partial<GriffelRenderer> = { id: 'a', insertCSSRules: jest.fn() };
11+
const rendererB: Partial<GriffelRenderer> = { id: 'b', insertCSSRules: jest.fn() };
12+
13+
const insertStyles = insertionFactory();
14+
15+
insertStyles(rendererA as GriffelRenderer, { d: ['a'] });
16+
insertStyles(rendererA as GriffelRenderer, { d: ['a'] });
17+
18+
expect(rendererA.insertCSSRules).toHaveBeenCalledTimes(1);
19+
20+
insertStyles(rendererB as GriffelRenderer, { d: ['a'] });
21+
insertStyles(rendererB as GriffelRenderer, { d: ['a'] });
22+
23+
expect(rendererB.insertCSSRules).toHaveBeenCalledTimes(1);
24+
});
25+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { CSSRulesByBucket, GriffelInsertionFactory, GriffelRenderer } from './types';
2+
3+
/**
4+
* Default implementation of insertion factory. Inserts styles only once per renderer and performs
5+
* insertion immediately after styles computation.
6+
*
7+
* @internal
8+
*/
9+
export const insertionFactory: GriffelInsertionFactory = () => {
10+
const insertionCache: Record<string, boolean> = {};
11+
12+
return function insertStyles(renderer: GriffelRenderer, cssRules: CSSRulesByBucket) {
13+
if (insertionCache[renderer.id] === undefined) {
14+
renderer.insertCSSRules(cssRules!);
15+
insertionCache[renderer.id] = true;
16+
}
17+
};
18+
};

packages/core/src/makeResetStyles.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import type { GriffelResetStyle } from '@griffel/style-types';
22

33
import { DEBUG_RESET_CLASSES } from './constants';
4+
import { insertionFactory } from './insertionFactory';
45
import { resolveResetStyleRules } from './runtime/resolveResetStyleRules';
56
import type { GriffelRenderer } from './types';
7+
import type { GriffelInsertionFactory } from './types';
68

79
export interface MakeResetStylesOptions {
810
dir: 'ltr' | 'rtl';
911
renderer: GriffelRenderer;
1012
}
1113

12-
export function makeResetStyles(styles: GriffelResetStyle) {
13-
const insertionCache: Record<string, boolean> = {};
14+
export function makeResetStyles(styles: GriffelResetStyle, factory: GriffelInsertionFactory = insertionFactory) {
15+
const insertStyles = factory();
1416

1517
let ltrClassName: string | null = null;
1618
let rtlClassName: string | null = null;
@@ -24,16 +26,9 @@ export function makeResetStyles(styles: GriffelResetStyle) {
2426
[ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(styles);
2527
}
2628

27-
const isLTR = dir === 'ltr';
28-
// As RTL classes are different they should have a different cache key for insertion
29-
const rendererId = isLTR ? renderer.id : renderer.id + 'r';
29+
insertStyles(renderer, { r: cssRules! });
3030

31-
if (insertionCache[rendererId] === undefined) {
32-
renderer.insertCSSRules({ r: cssRules! });
33-
insertionCache[rendererId] = true;
34-
}
35-
36-
const className = isLTR ? ltrClassName : rtlClassName || ltrClassName;
31+
const className = dir === 'ltr' ? ltrClassName : rtlClassName || ltrClassName;
3732

3833
if (process.env.NODE_ENV !== 'production') {
3934
DEBUG_RESET_CLASSES[className] = 1;

packages/core/src/makeStaticStyles.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
import type { GriffelStaticStyles } from '@griffel/style-types';
22

3+
import { insertionFactory } from './insertionFactory';
34
import { resolveStaticStyleRules } from './runtime/resolveStaticStyleRules';
45
import type { GriffelRenderer } from './types';
6+
import type { GriffelInsertionFactory } from './types';
57

68
export interface MakeStaticStylesOptions {
79
renderer: GriffelRenderer;
810
}
911

10-
/**
11-
* Register static css.
12-
* @param styles - styles object or string.
13-
*/
14-
export function makeStaticStyles(styles: GriffelStaticStyles | GriffelStaticStyles[]) {
15-
const styleCache: Record<string, true> = {};
12+
export function makeStaticStyles(
13+
styles: GriffelStaticStyles | GriffelStaticStyles[],
14+
factory: GriffelInsertionFactory = insertionFactory,
15+
) {
16+
const insertStyles = factory();
1617
const stylesSet: GriffelStaticStyles[] = Array.isArray(styles) ? styles : [styles];
1718

1819
function useStaticStyles(options: MakeStaticStylesOptions): void {
19-
const { renderer } = options;
20-
const cacheKey = renderer.id;
21-
22-
if (!styleCache[cacheKey]) {
23-
renderer.insertCSSRules({
24-
// 👇 static rules should be inserted into default bucket
25-
d: resolveStaticStyleRules(stylesSet),
26-
});
27-
styleCache[cacheKey] = true;
28-
}
20+
insertStyles(
21+
options.renderer,
22+
// 👇 static rules should be inserted into default bucket
23+
{ d: resolveStaticStyleRules(stylesSet) },
24+
);
2925
}
3026

3127
return useStaticStyles;

packages/core/src/makeStyles.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createDOMRenderer } from './renderer/createDOMRenderer';
22
import { griffelRendererSerializer } from './common/snapshotSerializers';
33
import { makeStyles } from './makeStyles';
4-
import { GriffelRenderer } from './types';
4+
import type { GriffelInsertionFactory, GriffelRenderer } from './types';
55

66
expect.addSnapshotSerializer(griffelRendererSerializer);
77

@@ -177,6 +177,30 @@ describe('makeStyles', () => {
177177
`);
178178
});
179179

180+
it('works with insertionFactory', () => {
181+
const insertionFactory: GriffelInsertionFactory = () => {
182+
return function (renderer, cssRulesByBucket) {
183+
renderer.insertCSSRules(cssRulesByBucket);
184+
};
185+
};
186+
const renderer: Partial<GriffelRenderer> = { insertCSSRules: jest.fn() };
187+
188+
const computeClasses = makeStyles(
189+
{
190+
root: { display: 'flex', paddingLeft: '10px' },
191+
},
192+
insertionFactory,
193+
);
194+
const classes = computeClasses({ dir: 'ltr', renderer: renderer as GriffelRenderer }).root;
195+
196+
expect(classes).toMatchInlineSnapshot(`"___qs05so0 f22iagw frdkuqy"`);
197+
198+
expect(renderer.insertCSSRules).toHaveBeenCalledTimes(1);
199+
expect(renderer.insertCSSRules).toHaveBeenCalledWith({
200+
d: ['.f22iagw{display:flex;}', '.frdkuqy{padding-left:10px;}', '.f81rol6{padding-right:10px;}'],
201+
});
202+
});
203+
180204
it('handles numeric slot names', () => {
181205
const computeClasses = makeStyles({
182206
42: {

packages/core/src/makeStyles.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { debugData, isDevToolsEnabled, getSourceURLfromError } from './devtools';
2+
import { insertionFactory } from './insertionFactory';
23
import { resolveStyleRulesForSlots } from './resolveStyleRulesForSlots';
34
import { reduceToClassNameForSlots } from './runtime/reduceToClassNameForSlots';
45
import type { CSSClassesMapBySlot, CSSRulesByBucket, GriffelRenderer, StylesBySlots } from './types';
6+
import type { GriffelInsertionFactory } from './types';
57

68
export interface MakeStylesOptions {
79
dir: 'ltr' | 'rtl';
810
renderer: GriffelRenderer;
911
}
1012

11-
export function makeStyles<Slots extends string | number>(stylesBySlots: StylesBySlots<Slots>) {
12-
const insertionCache: Record<string, boolean> = {};
13+
export function makeStyles<Slots extends string | number>(
14+
stylesBySlots: StylesBySlots<Slots>,
15+
factory: GriffelInsertionFactory = insertionFactory,
16+
) {
17+
const insertStyles = factory();
1318

1419
let classesMapBySlot: CSSClassesMapBySlot<Slots> | null = null;
1520
let cssRules: CSSRulesByBucket | null = null;
@@ -30,8 +35,6 @@ export function makeStyles<Slots extends string | number>(stylesBySlots: StylesB
3035
}
3136

3237
const isLTR = dir === 'ltr';
33-
// As RTL classes are different they should have a different cache key for insertion
34-
const rendererId = isLTR ? renderer.id : renderer.id + 'r';
3538

3639
if (isLTR) {
3740
if (ltrClassNamesForSlots === null) {
@@ -43,10 +46,7 @@ export function makeStyles<Slots extends string | number>(stylesBySlots: StylesB
4346
}
4447
}
4548

46-
if (insertionCache[rendererId] === undefined) {
47-
renderer.insertCSSRules(cssRules!);
48-
insertionCache[rendererId] = true;
49-
}
49+
insertStyles(renderer, cssRules!);
5050

5151
const classNamesForSlots = isLTR
5252
? (ltrClassNamesForSlots as Record<Slots, string>)

0 commit comments

Comments
 (0)