Skip to content

Commit

Permalink
fix(core): makeResetStyles emits rules into separate buckets (#405)
Browse files Browse the repository at this point in the history
  • Loading branch information
layershifter committed Jul 31, 2023
1 parent bd37c4f commit c33da85
Show file tree
Hide file tree
Showing 27 changed files with 274 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: makeResetStyles emits at rules into a separate bucket",
"packageName": "@griffel/core",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "chore: add support for different buckets in makeResetStyles",
"packageName": "@griffel/react",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "chore: add support for different buckets in makeResetStyles",
"packageName": "@griffel/webpack-extraction-plugin",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { makeResetStyles } from '@griffel/react';

export const useStyles = makeResetStyles({
color: 'red',
'@supports (display: flex)': { color: 'pink' },
'@media (min-width: 100px)': { color: 'blue' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { __resetStyles } from '@griffel/react';
export const useStyles = __resetStyles('rjrhw4c', null, {
r: ['.rjrhw4c{color:red;}'],
s: ['@supports (display: flex){.rjrhw4c{color:pink;}}', '@media (min-width: 100px){.rjrhw4c{color:blue;}}'],
});
5 changes: 5 additions & 0 deletions packages/babel-preset/src/transformPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ pluginTester({
fixture: path.resolve(fixturesDir, 'assets-reset-styles', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'assets-reset-styles', 'output.ts'),
},
{
title: 'reset: at rules',
fixture: path.resolve(fixturesDir, 'reset-styles-at-rules', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'reset-styles-at-rules', 'output.ts'),
},

// Imports
//
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/__resetStyles.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { DEBUG_RESET_CLASSES } from './constants';
import { insertionFactory } from './insertionFactory';
import type { MakeResetStylesOptions } from './makeResetStyles';
import type { GriffelInsertionFactory } from './types';
import type { CSSRulesByBucket, GriffelInsertionFactory } from './types';

/**
* @internal
*/
export function __resetStyles(
ltrClassName: string,
rtlClassName: string | null,
cssRules: string[],
cssRules: CSSRulesByBucket | string[],
factory: GriffelInsertionFactory = insertionFactory,
) {
const insertStyles = factory();
Expand All @@ -18,7 +18,7 @@ export function __resetStyles(
const { dir, renderer } = options;
const className = dir === 'ltr' ? ltrClassName : rtlClassName || ltrClassName;

insertStyles(renderer, { r: cssRules });
insertStyles(renderer, Array.isArray(cssRules) ? { r: cssRules! } : cssRules!);

if (process.env.NODE_ENV !== 'production') {
DEBUG_RESET_CLASSES[className] = 1;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/common/snapshotSerializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ export const griffelResetRulesSerializer: jest.SnapshotSerializerPlugin = {
* test function makes sure that value is the guarded type
*/
const _value = value as ReturnType<typeof resolveResetStyleRules>;
const cssRulesByBucket: CSSRulesByBucket = { r: _value[2] };
const cssRulesByBucket: CSSRulesByBucket = Array.isArray(_value[2]) ? { r: _value[2] } : _value[2];

return Object.entries(cssRulesByBucket)
.filter(([key, value]) => value.length > 0)
.flatMap(([key, value]) => [`/** bucket "${key}" */`, format(value as string[])])
.join('\n');
},
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/makeResetStyles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,25 @@ describe('makeResetStyles', () => {
}
`);
});

it('handles at rules', () => {
const computeClassName = makeResetStyles({
color: 'red',
'@media (min-width: 100px)': { color: 'blue' },
});

expect(computeClassName({ dir: 'ltr', renderer })).toEqual('rbwcbv2');
expect(renderer).toMatchInlineSnapshot(`
/** bucket "r" **/
.rbwcbv2 {
color: red;
}
/** bucket "s" **/
@media (min-width: 100px) {
.rbwcbv2 {
color: blue;
}
}
`);
});
});
7 changes: 3 additions & 4 deletions packages/core/src/makeResetStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { GriffelResetStyle } from '@griffel/style-types';
import { DEBUG_RESET_CLASSES } from './constants';
import { insertionFactory } from './insertionFactory';
import { resolveResetStyleRules } from './runtime/resolveResetStyleRules';
import type { GriffelRenderer } from './types';
import type { GriffelInsertionFactory } from './types';
import type { CSSRulesByBucket, GriffelRenderer, GriffelInsertionFactory } from './types';

export interface MakeResetStylesOptions {
dir: 'ltr' | 'rtl';
Expand All @@ -17,7 +16,7 @@ export function makeResetStyles(styles: GriffelResetStyle, factory: GriffelInser
let ltrClassName: string | null = null;
let rtlClassName: string | null = null;

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

function computeClassName(options: MakeResetStylesOptions): string {
const { dir, renderer } = options;
Expand All @@ -26,7 +25,7 @@ export function makeResetStyles(styles: GriffelResetStyle, factory: GriffelInser
[ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(styles);
}

insertStyles(renderer, { r: cssRules! });
insertStyles(renderer, Array.isArray(cssRules) ? { r: cssRules! } : cssRules!);

const className = dir === 'ltr' ? ltrClassName : rtlClassName || ltrClassName;

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/renderer/getStyleSheetForBucket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('getStyleSheetForBucket', () => {
getStyleSheetForBucket('t', target, null, renderer);
getStyleSheetForBucket('k', target, null, renderer);
getStyleSheetForBucket('f', target, null, renderer);
getStyleSheetForBucket('s', target, null, renderer);

const styleElements = target.head.querySelectorAll(`[${DATA_BUCKET_ATTR}]`);
const styleElementOrder = Array.from(styleElements).map(styleElement =>
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/renderer/getStyleSheetForBucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const styleBucketOrdering: StyleBucketName[] = [
'h',
// active
'a',
// at rules for reset styles
's',
// keyframes
'k',
// at-rules
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/runtime/compileResetCSSRules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { compileResetCSSRules } from './compileResetCSSRules';

describe('compileCSSRules', () => {
it('compiles CSS rules', () => {
const cssRules = `
.foo {
color: red;
@media (min-width: 768px) { color: blue }
}
`;

expect(compileResetCSSRules(cssRules)).toMatchInlineSnapshot(`
Array [
Array [
".foo{color:red;}",
],
Array [
"@media (min-width: 768px){.foo{color:blue;}}",
],
]
`);
});
});
34 changes: 34 additions & 0 deletions packages/core/src/runtime/compileResetCSSRules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { compile, middleware, serialize, stringify } from 'stylis';

import { globalPlugin } from './stylis/globalPlugin';
import { isAtRuleElement } from './stylis/isAtRuleElement';
import { prefixerPlugin } from './stylis/prefixerPlugin';
import { rulesheetPlugin } from './stylis/rulesheetPlugin';

export function compileResetCSSRules(cssRules: string): [string[], string[]] {
const rules: string[] = [];
const atRules: string[] = [];

serialize(
compile(cssRules),
middleware([
globalPlugin,
prefixerPlugin,
stringify,

// 💡 we are using `.insertRule()` API for DOM operations, which does not support
// insertion of multiple CSS rules in a single call. `rulesheet` plugin extracts
// individual rules to be used with this API
rulesheetPlugin((element, rule) => {
if (isAtRuleElement(element)) {
atRules.push(rule);
return;
}

rules.push(rule);
}),
]),
);

return [rules, atRules];
}
11 changes: 6 additions & 5 deletions packages/core/src/runtime/resolveResetStyleRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('resolveResetStyleRules', () => {
});

expect(result).toMatchInlineSnapshot(`
/** bucket "r" */
/** bucket "s" */
@container foo (max-width: 1px) {
.rmph5rz {
color: orange;
Expand All @@ -119,7 +119,7 @@ describe('resolveResetStyleRules', () => {
});

expect(result).toMatchInlineSnapshot(`
/** bucket "r" */
/** bucket "s" */
@container (max-width: 1px) {
.r1ph1abo {
color: orange;
Expand All @@ -144,6 +144,7 @@ describe('resolveResetStyleRules', () => {
.rpycl1b {
color: red;
}
/** bucket "s" */
@media (forced-colors: active) {
.rpycl1b {
color: orange;
Expand All @@ -166,7 +167,7 @@ describe('resolveResetStyleRules', () => {
});

expect(result).toMatchInlineSnapshot(`
/** bucket "r" */
/** bucket "s" */
@layer utilities {
.rvhnavh {
color: orange;
Expand All @@ -189,7 +190,7 @@ describe('resolveResetStyleRules', () => {
});

expect(result).toMatchInlineSnapshot(`
/** bucket "r" */
/** bucket "s" */
@supports (display: flex) {
.rxf8lon {
color: orange;
Expand All @@ -213,7 +214,7 @@ describe('resolveResetStyleRules', () => {
});

expect(result).toMatchInlineSnapshot(`
/** bucket "r" */
/** bucket "s" */
@supports (display: flex) {
.rhd25ja {
color: pink;
Expand Down
21 changes: 15 additions & 6 deletions packages/core/src/runtime/resolveResetStyleRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import type { GriffelResetStyle, GriffelAnimation } from '@griffel/style-types';
import { convert, convertProperty } from 'rtl-css-js/core';

import { RESET_HASH_PREFIX } from '../constants';
import type { CSSRulesByBucket } from '../types';
import { isMediaQuerySelector } from './utils/isMediaQuerySelector';
import { isLayerSelector } from './utils/isLayerSelector';
import { isNestedSelector } from './utils/isNestedSelector';
import { isSupportQuerySelector } from './utils/isSupportQuerySelector';
import { isObject } from './utils/isObject';
import { hyphenateProperty } from './utils/hyphenateProperty';
import { normalizePseudoSelector } from './compileAtomicCSSRule';
import { compileCSSRules } from './compileCSSRules';
import { compileResetCSSRules } from './compileResetCSSRules';
import { compileKeyframeRule, compileKeyframesCSS } from './compileKeyframeCSS';
import { isContainerQuerySelector } from './utils/isContainerQuerySelector';
import { warnAboutUnresolvedRule } from './warnings/warnAboutUnresolvedRule';
Expand Down Expand Up @@ -135,18 +136,26 @@ function createStringFromStyles(styles: GriffelResetStyle) {
/**
* @internal
*/
export function resolveResetStyleRules(styles: GriffelResetStyle): [string, string | null, string[]] {
export function resolveResetStyleRules(
styles: GriffelResetStyle,
): [string, string | null, CSSRulesByBucket | string[]] {
const [ltrRule, rtlRule] = createStringFromStyles(styles);

const ltrClassName = RESET_HASH_PREFIX + hashString(ltrRule);
const ltrCSS = compileCSSRules(`.${ltrClassName}{${ltrRule}}`, false);
const [ltrCSS, ltrCSSAtRules] = compileResetCSSRules(`.${ltrClassName}{${ltrRule}}`);

const hasAtRules = ltrCSSAtRules.length > 0;

if (ltrRule === rtlRule) {
return [ltrClassName, null, ltrCSS];
return [ltrClassName, null, hasAtRules ? { r: ltrCSS, s: ltrCSSAtRules } : ltrCSS];
}

const rtlClassName = RESET_HASH_PREFIX + hashString(rtlRule);
const rtlCSS = compileCSSRules(`.${rtlClassName}{${rtlRule}}`, false);
const [rtlCSS, rtlCSSAtRules] = compileResetCSSRules(`.${rtlClassName}{${rtlRule}}`);

return [ltrClassName, rtlClassName, ltrCSS.concat(rtlCSS)];
return [
ltrClassName,
rtlClassName,
hasAtRules ? { r: ltrCSS.concat(rtlCSS), s: ltrCSSAtRules.concat(rtlCSSAtRules) } : ltrCSS.concat(rtlCSS),
];
}
15 changes: 15 additions & 0 deletions packages/core/src/runtime/stylis/isAtRuleElement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { compile } from 'stylis';
import { isAtRuleElement } from './isAtRuleElement';

describe('isAtRuleElement', () => {
it.each([
['@container { div { color: red } }', true],
['@layer foo { div { color: red } }', true],
['@media (min-width: 100px) { div { color: red } }', true],
['@supports (display: flex) { div { color: red } }', true],
['div { color: red }', false],
['div:hover { color: red }', false],
])('handles "%s"', (css, expected) => {
expect(isAtRuleElement(compile(css)[0])).toBe(expected);
});
});
14 changes: 14 additions & 0 deletions packages/core/src/runtime/stylis/isAtRuleElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LAYER, MEDIA, SUPPORTS } from 'stylis';
import type { Element } from 'stylis';

export function isAtRuleElement(element: Element): boolean {
switch (element.type) {
case '@container':
case MEDIA:
case SUPPORTS:
case LAYER:
return true;
}

return false;
}
30 changes: 30 additions & 0 deletions packages/core/src/runtime/stylis/rulesheetPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { compile, middleware, serialize, stringify, RULESET } from 'stylis';

import { rulesheetPlugin } from './rulesheetPlugin';
import type { RulesheetPluginCallback } from './rulesheetPlugin';

function compileRule(rule: string, callback: RulesheetPluginCallback) {
return serialize(compile(rule), middleware([stringify, rulesheetPlugin(callback)]));
}

describe('rulesheetPlugin', () => {
it('handles basic selectors', () => {
const callback = jest.fn();
const css = `
.foo { color: red }
.bar { color: blue }
`;

expect(compileRule(css, callback)).toMatchInlineSnapshot(`".foo{color:red;}.bar{color:blue;}"`);

expect(callback).toHaveBeenCalledTimes(2);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({ type: RULESET, return: '.foo{color:red;}' }),
'.foo{color:red;}',
);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({ type: RULESET, return: '.bar{color:blue;}' }),
'.bar{color:blue;}',
);
});
});
16 changes: 16 additions & 0 deletions packages/core/src/runtime/stylis/rulesheetPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Element, Middleware } from 'stylis';

export type RulesheetPluginCallback = (type: Element, rule: string) => void;

/**
* The same plugin as in stylis, but this version also has "element" argument.
*/
export function rulesheetPlugin(callback: RulesheetPluginCallback): Middleware {
return function (element) {
if (!element.root) {
if (element.return) {
callback(element, element.return);
}
}
};
}

0 comments on commit c33da85

Please sign in to comment.