Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for retrieving StyleRule objects by className #1371

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hungry-trainers-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@vanilla-extract/css': minor
---

Add CSS cache to enable retrieving original style rule objects by className
7 changes: 7 additions & 0 deletions packages/css/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cssCache } from './cssCache';
import type { Adapter } from './types';

export const mockAdapter: Adapter = {
Expand Down Expand Up @@ -43,6 +44,12 @@ export const removeAdapter = () => {
};

export const appendCss: Adapter['appendCss'] = (...props) => {
const cssDef = props[0];
if (cssDef.type === 'local' || cssDef.type === 'global') {
const { selector, rule } = cssDef;
cssCache.set(selector, rule);
}

return currentAdapter().appendCss(...props);
};

Expand Down
39 changes: 39 additions & 0 deletions packages/css/src/cssCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cssCache } from './cssCache';

describe('cssCache', () => {
it('Supports adding/retrieving style rule associated with a className to/from cache', () => {
// throws if className string has more than one class embedded
expect(() => cssCache.set('two classes', {})).toThrow(
'Invalid className "two classes": found multiple classNames.',
);
// add to cache
expect(cssCache.size).toBe(0);
cssCache.set('test0', { content: 'test0' });
expect(cssCache.size).toBe(1);
cssCache.set('test1', { content: 'test1' });
expect(cssCache.size).toBe(2);

// throws if calling get with multiple classNames embedded in string
expect(() => cssCache.get('test0 test1')).toThrow(
'Invalid className "test0 test1": found multiple classNames, try CssCache.getAll() instead.',
);

// retrieve from cache
expect(cssCache.get('test0')).toEqual({ content: 'test0' });
// check exists in cache
expect(cssCache.has('test1')).toBe(true);
});

it('Supports retrieving StyleRule objects for a list fo classNames in original definition order', () => {
cssCache.set('test0', { content: 'test0' });
cssCache.set('test1', { content: 'test1' });
cssCache.set('test2', { content: 'test2' });

// retrieves style rule object for classNames in original order, ignore uncached values
expect(cssCache.getAll('test2 not-in-cache test0', 'test1')).toEqual([
{ content: 'test0' },
{ content: 'test1' },
{ content: 'test2' },
]);
});
});
67 changes: 67 additions & 0 deletions packages/css/src/cssCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { StyleRule } from './types';

type CssCacheValue = { rule: Readonly<StyleRule>; index: number };

let currentClassNameIndex = 0;
const cache = new Map<string, CssCacheValue>();

// ensures duplicated rules are applied in the same order they were created in
export const classNameSortCompareFn = (a: string, b: string) =>
(cache.get(a)?.index ?? 0) - (cache.get(b)?.index ?? 0);

export const cssCache = {
forEach(
callbackFn: (value: Readonly<StyleRule>, className: string) => void,
): void {
cache.forEach((value, className) => callbackFn(value.rule, className));
},

get(className: string): Readonly<StyleRule> | undefined {
if (className.split(' ').length > 1) {
throw new Error(
`Invalid className "${className}": found multiple classNames, try CssCache.getAll() instead.`,
);
}

return cache.get(className)?.rule;
},

getAll(...classNames: string[]): Readonly<StyleRule>[] {
const normalizedClassNames: string[] = [];
for (const className of classNames) {
className.split(' ').forEach((singleClassName) => {
const trimmedSingleClassName = singleClassName.trim();
if (trimmedSingleClassName) {
normalizedClassNames.push(trimmedSingleClassName);
}
});
}

return normalizedClassNames
.sort(classNameSortCompareFn)
.map((className) => cache.get(className)?.rule)
.filter((rule) => rule !== undefined) as Readonly<StyleRule>[];
},

has(className: string): boolean {
return cache.has(className);
},

set(className: string, rule: StyleRule): void {
if (className.split(' ').length > 1) {
throw new Error(
`Invalid className "${className}": found multiple classNames.`,
);
}
if (!cache.has(className)) {
cache.set(className, { rule, index: currentClassNameIndex });
currentClassNameIndex += 1;
}
},

get size() {
return cache.size;
},
};

export type CssCache = typeof cssCache;
1 change: 1 addition & 0 deletions packages/css/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './vars';
export { createContainer } from './container';
export { createViewTransition } from './viewTransition';
export * from './layer';
export * from './cssCache';