-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
stylis.js
98 lines (82 loc) 路 3.12 KB
/
stylis.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import Stylis from '@emotion/stylis';
import _insertRulePlugin from 'stylis-rule-sheet';
import { EMPTY_ARRAY, EMPTY_OBJECT } from './empties';
const COMMENT_REGEX = /^\s*\/\/.*$/gm;
export type Stringifier = (
css: string,
selector: string,
prefix: ?string,
componentId: string
) => Array<string>;
type StylisInstanceConstructorArgs = {
options?: Object,
plugins?: Array<Function>,
};
export default function createStylisInstance({
options = EMPTY_OBJECT,
plugins = EMPTY_ARRAY,
}: StylisInstanceConstructorArgs = EMPTY_OBJECT) {
const stylis = new Stylis(options);
// Wrap `insertRulePlugin to build a list of rules,
// and then make our own plugin to return the rules. This
// makes it easier to hook into the existing SSR architecture
let parsingRules = [];
// eslint-disable-next-line consistent-return
const returnRulesPlugin = context => {
if (context === -2) {
const parsedRules = parsingRules;
parsingRules = [];
return parsedRules;
}
};
const parseRulesPlugin = _insertRulePlugin(rule => {
parsingRules.push(rule);
});
let _componentId: string;
let _selector: string;
let _selectorRegexp: RegExp;
const selfReferenceReplacer = (match, offset, string) => {
if (
// the first self-ref is always untouched
offset > 0 &&
// there should be at least two self-refs to do a replacement (.b > .b)
string.slice(0, offset).indexOf(_selector) !== -1 &&
// no consecutive self refs (.b.b); that is a precedence boost and treated differently
string.slice(offset - _selector.length, offset) !== _selector
) {
return `.${_componentId}`;
}
return match;
};
/**
* When writing a style like
*
* & + & {
* color: red;
* }
*
* The second ampersand should be a reference to the static component class. stylis
* has no knowledge of static class so we have to intelligently replace the base selector.
*
* https://github.com/thysultan/stylis.js#plugins <- more info about the context phase values
* "2" means this plugin is taking effect at the very end after all other processing is complete
*/
const selfReferenceReplacementPlugin = (context, _, selectors) => {
if (context === 2 && selectors.length && selectors[0].lastIndexOf(_selector) > 0) {
// eslint-disable-next-line no-param-reassign
selectors[0] = selectors[0].replace(_selectorRegexp, selfReferenceReplacer);
}
};
stylis.use([...plugins, selfReferenceReplacementPlugin, parseRulesPlugin, returnRulesPlugin]);
return function stringifyRules(css, selector, prefix, componentId = '&'): Stringifier {
const flatCSS = css.replace(COMMENT_REGEX, '');
const cssStr = selector && prefix ? `${prefix} ${selector} { ${flatCSS} }` : flatCSS;
// stylis has no concept of state to be passed to plugins
// but since JS is single=threaded, we can rely on that to ensure
// these properties stay in sync with the current stylis run
_componentId = componentId;
_selector = selector;
_selectorRegexp = new RegExp(`\\${_selector}\\b`, 'g');
return stylis(prefix || !selector ? '' : selector, cssStr);
};
}