Skip to content

Commit 3cd13ab

Browse files
authored
Inline @emotion/css to avoid dependency on Babel-related dependencies (#1566)
1 parent ca19dd2 commit 3cd13ab

9 files changed

Lines changed: 13786 additions & 4645 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@keystar/ui': minor
3+
---
4+
5+
`@keystar/ui` class names are now prefixed with `kui-` and use an internal
6+
implementation of `@emotion/css` to avoid dependencies on a large number of
7+
Babel-related dependencies

design-system/pkg/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,10 @@
14741474
},
14751475
"dependencies": {
14761476
"@babel/runtime": "^7.18.3",
1477-
"@emotion/css": "^11.13.5",
1477+
"@emotion/cache": "^11.14.0",
1478+
"@emotion/serialize": "^1.3.3",
1479+
"@emotion/sheet": "^1.4.0",
1480+
"@emotion/utils": "^1.4.2",
14781481
"@floating-ui/react": "^0.24.0",
14791482
"@internationalized/date": "^3.12.2",
14801483
"@internationalized/number": "^3.6.7",

design-system/pkg/src/style/classNames.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cx } from '@emotion/css';
1+
import { cx } from './emotion';
22
import { TOKEN_PREFIX } from '@keystar/ui/primitives';
33
import { assert } from 'emery';
44

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// https://github.com/emotion-js/emotion/blob/b882bcba85132554992e4bd49e94c95939bbf810/packages/css/src/create-instance.ts
2+
// to avoid @emotion/css's dependencies on @emotion/babel-plugin + babel-plugin-macros and the large number of transitive deps that introduces
3+
import createCache, { EmotionCache, Options } from '@emotion/cache';
4+
import {
5+
serializeStyles,
6+
CSSInterpolation,
7+
Interpolation,
8+
} from '@emotion/serialize';
9+
import {
10+
insertStyles,
11+
getRegisteredStyles,
12+
SerializedStyles,
13+
RegisteredCache,
14+
} from '@emotion/utils';
15+
import { StyleSheet } from '@emotion/sheet';
16+
import { TOKEN_PREFIX } from '@keystar/ui/primitives';
17+
18+
export type {
19+
CSSInterpolation,
20+
ArrayCSSInterpolation,
21+
ComponentSelector,
22+
CSSObject,
23+
} from '@emotion/serialize';
24+
25+
function insertWithoutScoping(
26+
cache: EmotionCache,
27+
serialized: SerializedStyles
28+
) {
29+
if (cache.inserted[serialized.name] === undefined) {
30+
return cache.insert('', serialized, cache.sheet, true);
31+
}
32+
}
33+
34+
export type { EmotionCache, Options };
35+
36+
export interface ArrayClassNamesArg extends Array<ClassNamesArg> {}
37+
export type ClassNamesArg =
38+
| undefined
39+
| null
40+
| string
41+
| boolean
42+
| { [className: string]: boolean | null | undefined }
43+
| ArrayClassNamesArg;
44+
45+
export interface CSSStyleSheet extends StyleSheet {
46+
speedy(value: boolean): void;
47+
}
48+
49+
export interface Emotion {
50+
css(template: TemplateStringsArray, ...args: Array<CSSInterpolation>): string;
51+
css(...args: Array<CSSInterpolation>): string;
52+
cx(...classNames: Array<ClassNamesArg>): string;
53+
flush(): void;
54+
hydrate(ids: Array<string>): void;
55+
injectGlobal(
56+
template: TemplateStringsArray,
57+
...args: Array<CSSInterpolation>
58+
): void;
59+
injectGlobal(...args: Array<CSSInterpolation>): void;
60+
keyframes(
61+
template: TemplateStringsArray,
62+
...args: Array<CSSInterpolation>
63+
): string;
64+
keyframes(...args: Array<CSSInterpolation>): string;
65+
sheet: CSSStyleSheet;
66+
cache: EmotionCache;
67+
merge(className: string): string;
68+
getRegisteredStyles(
69+
registeredStyles: Array<string>,
70+
className: string
71+
): string;
72+
}
73+
74+
function merge(
75+
registered: RegisteredCache,
76+
css: Emotion['css'],
77+
className: string
78+
) {
79+
const registeredStyles: string[] = [];
80+
const rawClassName = getRegisteredStyles(
81+
registered,
82+
registeredStyles,
83+
className
84+
);
85+
86+
if (registeredStyles.length < 2) {
87+
return className;
88+
}
89+
return rawClassName + css(registeredStyles);
90+
}
91+
92+
let createEmotion = (options: Options): Emotion => {
93+
let cache = createCache(options);
94+
95+
(cache.sheet as CSSStyleSheet).speedy = function (value: boolean) {
96+
this.isSpeedy = value;
97+
};
98+
99+
cache.compat = true;
100+
101+
let css: Emotion['css'] = (
102+
...args: (TemplateStringsArray | Interpolation<unknown>)[]
103+
) => {
104+
let serialized = serializeStyles(args, cache.registered, undefined);
105+
insertStyles(cache, serialized, false);
106+
return `${cache.key}-${serialized.name}`;
107+
};
108+
109+
let keyframes: Emotion['keyframes'] = (
110+
...args: (TemplateStringsArray | Interpolation<unknown>)[]
111+
) => {
112+
let serialized = serializeStyles(args, cache.registered);
113+
let animation = `animation-${serialized.name}`;
114+
insertWithoutScoping(cache, {
115+
name: serialized.name,
116+
styles: `@keyframes ${animation}{${serialized.styles}}`,
117+
});
118+
119+
return animation;
120+
};
121+
let injectGlobal: Emotion['injectGlobal'] = (
122+
...args: (TemplateStringsArray | Interpolation<unknown>)[]
123+
) => {
124+
let serialized = serializeStyles(args, cache.registered);
125+
insertWithoutScoping(cache, serialized);
126+
};
127+
128+
let cx: Emotion['cx'] = (...args) => {
129+
return merge(cache.registered, css, classnames(args));
130+
};
131+
return {
132+
css,
133+
cx,
134+
injectGlobal,
135+
keyframes,
136+
hydrate(ids) {
137+
ids.forEach(key => {
138+
cache.inserted[key] = true;
139+
});
140+
},
141+
flush() {
142+
cache.registered = {};
143+
cache.inserted = {};
144+
cache.sheet.flush();
145+
},
146+
sheet: cache.sheet as CSSStyleSheet,
147+
cache,
148+
getRegisteredStyles: getRegisteredStyles.bind(null, cache.registered),
149+
merge: merge.bind(null, cache.registered, css),
150+
};
151+
};
152+
153+
let classnames = (args: ClassNamesArg[]) => {
154+
let cls = '';
155+
for (let i = 0; i < args.length; i++) {
156+
let arg = args[i];
157+
if (arg == null) continue;
158+
159+
let toAdd;
160+
switch (typeof arg) {
161+
case 'boolean':
162+
break;
163+
case 'object': {
164+
if (Array.isArray(arg)) {
165+
toAdd = classnames(arg);
166+
} else {
167+
toAdd = '';
168+
for (const k in arg) {
169+
if (arg[k] && k) {
170+
// eslint-disable-next-line no-unused-expressions
171+
toAdd && (toAdd += ' ');
172+
toAdd += k;
173+
}
174+
}
175+
}
176+
break;
177+
}
178+
default: {
179+
toAdd = arg;
180+
}
181+
}
182+
if (toAdd) {
183+
// eslint-disable-next-line no-unused-expressions
184+
cls && (cls += ' ');
185+
cls += toAdd;
186+
}
187+
}
188+
return cls;
189+
};
190+
191+
export const { cx, css, keyframes, injectGlobal, cache } = createEmotion({
192+
key: TOKEN_PREFIX,
193+
});

design-system/pkg/src/style/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { css, keyframes, injectGlobal, cache } from '@emotion/css'; // simplify dependencies + ensure the same version of emotion is used
1+
export { css, keyframes, injectGlobal, cache } from './emotion'; // simplify dependencies + ensure the same version of emotion is used
22

33
export { transition } from './animation';
44
export { resetClassName, classNames, ClassList } from './classNames';

design-system/pkg/src/style/responsive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ResponsiveProp,
99
StyleResolver,
1010
} from './types';
11-
import { CSSObject } from '@emotion/css/create-instance';
11+
import type { CSSObject } from '@emotion/serialize';
1212

1313
// Breakpoints
1414
// ----------------------------------------------------------------------------

design-system/pkg/src/style/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MaybeArray } from '@keystar/ui/types';
22
import { CSSProperties } from 'react';
33

44
import { tokenSchema } from './tokens';
5-
import { cx } from '@emotion/css';
5+
import type { cx } from './emotion';
66

77
export type CSSProp = keyof CSSProperties;
88
export type Primitive = number | string | boolean | null | undefined;

design-system/pkg/src/style/useStyleProps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { HTMLAttributes } from 'react';
22
import { warning } from 'emery';
3-
import { css } from '@emotion/css';
4-
import { CSSObject, CSSInterpolation } from '@emotion/css/create-instance';
3+
import { css } from './emotion';
54

65
import { defaultStyleProps } from './resolvers';
76
import {
@@ -11,6 +10,7 @@ import {
1110
} from './responsive';
1211
import { BaseStyleProps, BoxStyleProps, StyleResolverMap } from './types';
1312
import { classNames } from './classNames';
13+
import type { CSSInterpolation, CSSObject } from '@emotion/serialize';
1414

1515
// Convert
1616
// ----------------------------------------------------------------------------

0 commit comments

Comments
 (0)