@stylexjs/stylex 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.babelrc.js ADDED
@@ -0,0 +1,40 @@
1
+ function makeHaste() {
2
+ return {
3
+ visitor: {
4
+ ImportDeclaration(path) {
5
+ if (path.get('source').isStringLiteral()) {
6
+ const oldValue = path.get('source').node.value;
7
+ path.get('source').node.value = oldValue.slice(
8
+ oldValue.lastIndexOf('/') + 1
9
+ );
10
+ }
11
+ },
12
+ },
13
+ };
14
+ }
15
+
16
+ const presets = process.env['HASTE']
17
+ ? []
18
+ : [
19
+ [
20
+ '@babel/preset-env',
21
+ {
22
+ exclude: ['@babel/plugin-transform-typeof-symbol'],
23
+ targets: 'defaults',
24
+ },
25
+ ],
26
+ '@babel/preset-flow',
27
+ '@babel/preset-react',
28
+ ];
29
+
30
+ const plugins = process.env['HASTE']
31
+ ? [makeHaste, '@babel/plugin-syntax-flow', '@babel/plugin-syntax-jsx']
32
+ : [];
33
+
34
+ module.exports = {
35
+ assumptions: {
36
+ iterableIsArray: true,
37
+ },
38
+ presets,
39
+ plugins,
40
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ import { StyleXSheet } from '../src/StyleXSheet';
13
+
14
+ const testOpts = {
15
+ rootTheme: undefined,
16
+ supportsVariables: true,
17
+ };
18
+
19
+ test('StyleXSheet.prototype.insert', () => {
20
+ const sheet = new StyleXSheet(testOpts);
21
+
22
+ expect(sheet.getRuleCount()).toBe(0);
23
+ sheet.inject();
24
+ expect(sheet.getRuleCount()).toBe(0);
25
+
26
+ sheet.insert('.a {}', 0);
27
+ expect(sheet.getRuleCount()).toBe(1);
28
+
29
+ sheet.insert('.b {}', 0);
30
+ expect(sheet.getRuleCount()).toBe(2);
31
+
32
+ sheet.insert('.b {}', 0);
33
+ expect(sheet.getRuleCount()).toBe(2);
34
+ });
35
+
36
+ test('StyleXSheet.prototype.insert respects priorities', () => {
37
+ const sheet = new StyleXSheet(testOpts);
38
+
39
+ sheet.insert('.last {}', 6);
40
+ sheet.insert('.third {}', 3);
41
+ sheet.insert('.first {}', 0);
42
+ sheet.insert('.second {}', 1);
43
+
44
+ expect(sheet.getCSS()).toMatchInlineSnapshot(`
45
+ ".first {}
46
+ .second {}
47
+ .third {}
48
+ .last {}"
49
+ `);
50
+ });
51
+
52
+ test('StyleXSheet.prototype.insert respects priority floats', () => {
53
+ const sheet = new StyleXSheet(testOpts);
54
+
55
+ sheet.insert('.fourth {}', 6.8);
56
+ sheet.insert('.third {}', 6.5);
57
+ sheet.insert('.second {}', 6);
58
+ sheet.insert('.first {}', 2);
59
+
60
+ expect(sheet.getCSS()).toMatchInlineSnapshot(`
61
+ ".first {}
62
+ .second {}
63
+ .third {}
64
+ .fourth {}"
65
+ `);
66
+ });
67
+
68
+ test('StyleXSheet.prototype.insert handles RTL rules with media queries', () => {
69
+ const sheet = new StyleXSheet(testOpts);
70
+
71
+ sheet.insert(
72
+ '@media (min-width: 1000px) { .foo {} }',
73
+ 0,
74
+ '@media (min-width: 1000px) { .foo {} }'
75
+ );
76
+
77
+ expect(sheet.getCSS()).toMatchInlineSnapshot(`
78
+ "@media (min-width: 1000px) {html:not([dir='rtl']) .foo {} }
79
+ @media (min-width: 1000px) {html[dir='rtl'] .foo {} }"
80
+ `);
81
+ });
82
+
83
+ test('inlines variables for older browsers', () => {
84
+ const sheet = new StyleXSheet({
85
+ rootDarkTheme: { foo: 'reallydark' },
86
+ rootTheme: { foo: 'bar' },
87
+ supportsVariables: false,
88
+ });
89
+
90
+ sheet.insert('.foo {color: var(--foo)}', 1);
91
+
92
+ expect(sheet.getCSS()).toMatchInlineSnapshot(`
93
+ ":root, .__fb-light-mode {
94
+ --foo: bar;
95
+ }
96
+ .__fb-dark-mode:root, .__fb-dark-mode {
97
+ --foo: reallydark;
98
+ }
99
+ .foo {color: bar}"
100
+ `);
101
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @noflow
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ import { styleSheet } from '../src/StyleXSheet';
13
+ import stylex from '../src/stylex';
14
+
15
+ // TODO: priorities need testing
16
+ test('stylex.inject', () => {
17
+ const prevCount = styleSheet.getRuleCount();
18
+ stylex.inject('hey {}', 0);
19
+ expect(styleSheet.getRuleCount()).toBeGreaterThan(prevCount);
20
+ });
21
+
22
+ describe('stylex', () => {
23
+ test('basic resolve', () => {
24
+ expect(stylex({ a: 'aaa', b: 'bbb' })).toBe('aaa bbb');
25
+ });
26
+
27
+ test('merge order', () => {
28
+ expect(
29
+ stylex([
30
+ { a: 'a', ':hover': { aa: 'aa' } },
31
+ { b: 'b' },
32
+ { c: 'c', ':hover': { cc: 'cc' } },
33
+ ])
34
+ ).toBe('a aa b c cc');
35
+ });
36
+
37
+ test('with a top-level array of simple overridden classes', () => {
38
+ expect(
39
+ stylex([
40
+ {
41
+ backgroundColor: 'nu7423ey',
42
+ },
43
+ {
44
+ backgroundColor: 'gh25dzvf',
45
+ },
46
+ ])
47
+ ).toEqual('gh25dzvf');
48
+ });
49
+
50
+ test('with nested arrays and pseudoClasses overriding things', () => {
51
+ expect(
52
+ stylex([
53
+ {
54
+ backgroundColor: 'nu7423ey',
55
+ },
56
+ [
57
+ {
58
+ backgroundColor: 'abcdefg',
59
+ ':hover': {
60
+ backgroundColor: 'ksdfmwjs',
61
+ },
62
+ },
63
+ ],
64
+ {
65
+ color: 'gofk2cf1',
66
+ ':hover': {
67
+ backgroundColor: 'rse6dlih',
68
+ },
69
+ },
70
+ ])
71
+ ).toEqual('abcdefg gofk2cf1 rse6dlih');
72
+ });
73
+
74
+ test('with just pseudoclasses', () => {
75
+ expect(
76
+ stylex(
77
+ {
78
+ ':hover': {
79
+ backgroundColor: 'rse6dlih',
80
+ },
81
+ },
82
+ {
83
+ ':hover': {
84
+ color: 'gofk2cf1',
85
+ },
86
+ }
87
+ )
88
+ ).toEqual('rse6dlih gofk2cf1');
89
+ });
90
+
91
+ test('with complicated set of arguments', () => {
92
+ const styles = [
93
+ {
94
+ backgroundColor: 'nu7423ey',
95
+ borderColor: 'tpe1esc0',
96
+ borderStyle: 'gewhe1h2',
97
+ borderWidth: 'gcovof34',
98
+ boxSizing: 'bdao358l',
99
+ display: 'rse6dlih',
100
+ listStyle: 's5oniofx',
101
+ marginTop: 'm8h3af8h',
102
+ marginEnd: 'l7ghb35v',
103
+ marginBottom: 'kjdc1dyq',
104
+ marginStart: 'kmwttqpk',
105
+ paddingTop: 'srn514ro',
106
+ paddingEnd: 'oxkhqvkx',
107
+ paddingBottom: 'rl78xhln',
108
+ paddingStart: 'nch0832m',
109
+ WebkitTapHighlightColor: 'qi72231t',
110
+ textAlign: 'cr00lzj9',
111
+ textDecoration: 'rn8ck1ys',
112
+ whiteSpace: 'n3t5jt4f',
113
+ wordWrap: 'gh25dzvf',
114
+ zIndex: 'g4tp4svg',
115
+ },
116
+ false,
117
+ false,
118
+ false,
119
+ false,
120
+ [
121
+ {
122
+ cursor: 'fsf7x5fv',
123
+ touchAction: 's3jn8y49',
124
+ },
125
+ false,
126
+ {
127
+ outline: 'icdlwmnq',
128
+ },
129
+ [
130
+ {
131
+ WebkitTapHighlightColor: 'oajrlxb2',
132
+ cursor: 'nhd2j8a9',
133
+ touchAction: 'f1sip0of',
134
+ },
135
+ false,
136
+ false,
137
+ {
138
+ textDecoration: 'esuyzwwr',
139
+ ':hover': {
140
+ textDecoration: 'p8dawk7l',
141
+ },
142
+ },
143
+ false,
144
+ [
145
+ {
146
+ backgroundColor: 'g5ia77u1',
147
+ border: 'e4t7hp5w',
148
+ color: 'gmql0nx0',
149
+ cursor: 'nhd2j8a9',
150
+ display: 'q9uorilb',
151
+ fontFamily: 'ihxqhq3m',
152
+ fontSize: 'l94mrbxd',
153
+ lineHeight: 'aenfhxwr',
154
+ marginTop: 'kvgmc6g5',
155
+ marginEnd: 'cxmmr5t8',
156
+ marginBottom: 'oygrvhab',
157
+ marginStart: 'hcukyx3x',
158
+ paddingTop: 'jb3vyjys',
159
+ paddingEnd: 'rz4wbd8a',
160
+ paddingBottom: 'qt6c0cv9',
161
+ paddingStart: 'a8nywdso',
162
+ textAlign: 'i1ao9s8h',
163
+ textDecoration: 'myohyog2',
164
+ ':hover': {
165
+ color: 'ksdfmwjs',
166
+ textDecoration: 'gofk2cf1',
167
+ },
168
+ ':active': {
169
+ transform: 'lsqurvkf',
170
+ transition: 'bj9fd4vl',
171
+ },
172
+ },
173
+ {
174
+ display: 'a8c37x1j',
175
+ width: 'k4urcfbm',
176
+ },
177
+ [
178
+ {
179
+ ':active': {
180
+ transform: 'tm8avpzi',
181
+ },
182
+ },
183
+ ],
184
+ ],
185
+ ],
186
+ ],
187
+ ];
188
+
189
+ const value = stylex(styles);
190
+ const repeat = stylex(styles);
191
+
192
+ // Check the cached-derived result is correct
193
+ expect(value).toEqual(repeat);
194
+
195
+ expect(value.split(' ').sort().join(' ')).toEqual(
196
+ 'g5ia77u1 tpe1esc0 gewhe1h2 gcovof34 bdao358l a8c37x1j s5oniofx kvgmc6g5 cxmmr5t8 oygrvhab hcukyx3x jb3vyjys rz4wbd8a qt6c0cv9 a8nywdso oajrlxb2 i1ao9s8h myohyog2 n3t5jt4f gh25dzvf g4tp4svg nhd2j8a9 f1sip0of icdlwmnq e4t7hp5w gmql0nx0 ihxqhq3m l94mrbxd aenfhxwr k4urcfbm gofk2cf1 ksdfmwjs tm8avpzi bj9fd4vl'
197
+ .split(' ')
198
+ .sort()
199
+ .join(' ')
200
+ );
201
+ });
202
+ });
package/benchmark.js ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ const stylex = require('./lib/stylex').default;
9
+ const Benchmark = require('benchmark');
10
+ const suite = new Benchmark.Suite();
11
+ const test = (...args) => suite.add(...args);
12
+
13
+ const basicStyleFixture1 = {
14
+ backgroundColor: 'nu7423ey',
15
+ };
16
+
17
+ const basicStyleFixture2 = {
18
+ backgroundColor: 'gh25dzvf',
19
+ };
20
+
21
+ const bigStyleFixture = {
22
+ backgroundColor: 'nu7423ey',
23
+ borderColor: 'tpe1esc0',
24
+ borderStyle: 'gewhe1h2',
25
+ borderWidth: 'gcovof34',
26
+ boxSizing: 'bdao358l',
27
+ display: 'rse6dlih',
28
+ listStyle: 's5oniofx',
29
+ marginTop: 'm8h3af8h',
30
+ marginEnd: 'l7ghb35v',
31
+ marginBottom: 'kjdc1dyq',
32
+ marginStart: 'kmwttqpk',
33
+ paddingTop: 'srn514ro',
34
+ paddingEnd: 'oxkhqvkx',
35
+ paddingBottom: 'rl78xhln',
36
+ paddingStart: 'nch0832m',
37
+ textAlign: 'cr00lzj9',
38
+ textDecoration: 'rn8ck1ys',
39
+ whiteSpace: 'n3t5jt4f',
40
+ wordWrap: 'gh25dzvf',
41
+ zIndex: 'g4tp4svg',
42
+ };
43
+
44
+ const bigStyleWithPseudosFixture = {
45
+ backgroundColor: 'g5ia77u1',
46
+ border: 'e4t7hp5w',
47
+ color: 'gmql0nx0',
48
+ cursor: 'nhd2j8a9',
49
+ display: 'q9uorilb',
50
+ fontFamily: 'ihxqhq3m',
51
+ fontSize: 'l94mrbxd',
52
+ lineHeight: 'aenfhxwr',
53
+ marginTop: 'kvgmc6g5',
54
+ marginEnd: 'cxmmr5t8',
55
+ marginBottom: 'oygrvhab',
56
+ marginStart: 'hcukyx3x',
57
+ paddingTop: 'jb3vyjys',
58
+ paddingEnd: 'rz4wbd8a',
59
+ paddingBottom: 'qt6c0cv9',
60
+ paddingStart: 'a8nywdso',
61
+ textAlign: 'i1ao9s8h',
62
+ textDecoration: 'myohyog2',
63
+ ':hover': {
64
+ color: 'ksdfmwjs',
65
+ textDecoration: 'gofk2cf1',
66
+ },
67
+ ':active': {
68
+ transform: 'lsqurvkf',
69
+ transition: 'bj9fd4vl',
70
+ },
71
+ };
72
+
73
+ test('stylex(): basic', () => {
74
+ stylex(basicStyleFixture1);
75
+ });
76
+
77
+ test('stylex(): complex', () => {
78
+ stylex(bigStyleFixture);
79
+ });
80
+
81
+ test('stylex(): basic merge (args)', () => {
82
+ stylex(basicStyleFixture1, basicStyleFixture2);
83
+ });
84
+
85
+ test('stylex(): basic merge (array)', () => {
86
+ stylex([basicStyleFixture1, basicStyleFixture2]);
87
+ });
88
+
89
+ const complexNestedStyleFixture = [
90
+ bigStyleFixture,
91
+ false,
92
+ false,
93
+ false,
94
+ false,
95
+ [
96
+ {
97
+ cursor: 'fsf7x5fv',
98
+ touchAction: 's3jn8y49',
99
+ },
100
+ false,
101
+ {
102
+ outline: 'icdlwmnq',
103
+ },
104
+ [
105
+ {
106
+ cursor: 'nhd2j8a9',
107
+ touchAction: 'f1sip0of',
108
+ },
109
+ false,
110
+ false,
111
+ {
112
+ textDecoration: 'esuyzwwr',
113
+ ':hover': {
114
+ textDecoration: 'p8dawk7l',
115
+ },
116
+ },
117
+ false,
118
+ [
119
+ bigStyleWithPseudosFixture,
120
+ {
121
+ display: 'a8c37x1j',
122
+ width: 'k4urcfbm',
123
+ },
124
+ [
125
+ {
126
+ ':active': {
127
+ transform: 'tm8avpzi',
128
+ },
129
+ },
130
+ ],
131
+ ],
132
+ ],
133
+ ],
134
+ ];
135
+
136
+ test('stylex(): complex merge (array)', () => {
137
+ stylex([complexNestedStyleFixture]);
138
+ });
139
+
140
+ suite.on('cycle', (event) => {
141
+ console.log(String(event.target));
142
+ });
143
+
144
+ suite.run();
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ 'use strict';
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ Object.defineProperty(exports, "__esModule", {
13
+ value: true
14
+ });
15
+ exports.styleSheet = exports.StyleXSheet = void 0;
16
+ var _invariant = _interopRequireDefault(require("invariant"));
17
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
+ const LIGHT_MODE_CLASS_NAME = '__fb-light-mode';
20
+ const DARK_MODE_CLASS_NAME = '__fb-dark-mode';
21
+ /**
22
+ * Take a theme and generate it's accompanying CSS variables
23
+ */
24
+ function buildTheme(selector, theme) {
25
+ const lines = [];
26
+ lines.push(`${selector} {`);
27
+ for (const key in theme) {
28
+ const value = theme[key];
29
+ lines.push(` --${key}: ${value};`);
30
+ }
31
+ lines.push('}');
32
+ return lines.join('\n');
33
+ }
34
+
35
+ /**
36
+ * Create a <style> tag and add it to the head.
37
+ */
38
+ function makeStyleTag() {
39
+ const tag = document.createElement('style');
40
+ tag.setAttribute('type', 'text/css');
41
+ tag.setAttribute('data-stylex', 'true');
42
+ const head = document.head || document.getElementsByTagName('head')[0];
43
+ (0, _invariant.default)(head, 'expected head');
44
+ head.appendChild(tag);
45
+ return tag;
46
+ }
47
+
48
+ /**
49
+ * Check if the browser supports CSS variables
50
+ */
51
+ function doesSupportCSSVariables() {
52
+ return globalThis.CSS != null && globalThis.CSS.supports != null && globalThis.CSS.supports('--fake-var:0');
53
+ }
54
+
55
+ // Regex to replace var(--foo) with an inlined version
56
+ const VARIABLE_MATCH = /var\(--(.*?)\)/g;
57
+
58
+ // Stylesheet options
59
+
60
+ /**
61
+ * This class manages the CSS stylesheet for the page and the injection of new
62
+ * CSS rules.
63
+ */
64
+ class StyleXSheet {
65
+ constructor(opts) {
66
+ this.tag = null;
67
+ this.injected = false;
68
+ this.ruleForPriority = new Map();
69
+ this.rules = [];
70
+ this.rootTheme = opts.rootTheme;
71
+ this.rootDarkTheme = opts.rootDarkTheme;
72
+ this.supportsVariables = opts.supportsVariables ?? doesSupportCSSVariables();
73
+ }
74
+ getVariableMatch() {
75
+ return VARIABLE_MATCH;
76
+ }
77
+
78
+ /**
79
+ * Check if we have don't have access to the dom
80
+ */
81
+ isHeadless() {
82
+ var _globalThis$document;
83
+ return this.tag == null || (globalThis === null || globalThis === void 0 ? void 0 : (_globalThis$document = globalThis.document) === null || _globalThis$document === void 0 ? void 0 : _globalThis$document.body) == null;
84
+ }
85
+
86
+ /**
87
+ * Get the stylesheet tag. Throw if none exists.
88
+ */
89
+ getTag() {
90
+ const {
91
+ tag
92
+ } = this;
93
+ (0, _invariant.default)(tag != null, 'expected tag');
94
+ return tag;
95
+ }
96
+
97
+ /**
98
+ * Get the current stylesheet CSS.
99
+ */
100
+ getCSS() {
101
+ return this.rules.join('\n');
102
+ }
103
+
104
+ /**
105
+ * Get the position of the rule in the stylesheet.
106
+ */
107
+ getRulePosition(rule) {
108
+ return this.rules.indexOf(rule);
109
+ }
110
+
111
+ /**
112
+ * Count of the current rules in the stylesheet. Used in tests.
113
+ */
114
+ getRuleCount() {
115
+ return this.rules.length;
116
+ }
117
+
118
+ /**
119
+ * Inject a style tag into the document head
120
+ */
121
+ inject() {
122
+ var _globalThis$document2;
123
+ if (this.injected) {
124
+ return;
125
+ }
126
+ this.injected = true;
127
+
128
+ // Running in a server environment
129
+ if (((_globalThis$document2 = globalThis.document) === null || _globalThis$document2 === void 0 ? void 0 : _globalThis$document2.body) == null) {
130
+ this.injectTheme();
131
+ return;
132
+ }
133
+
134
+ // Create style tag if in browser
135
+ this.tag = makeStyleTag();
136
+ this.injectTheme();
137
+ }
138
+
139
+ /**
140
+ * Inject the theme styles
141
+ */
142
+ injectTheme() {
143
+ if (this.rootTheme != null) {
144
+ this.insert(buildTheme(`:root, .${LIGHT_MODE_CLASS_NAME}`, this.rootTheme), 0);
145
+ }
146
+ if (this.rootDarkTheme != null) {
147
+ this.insert(buildTheme(`.${DARK_MODE_CLASS_NAME}:root, .${DARK_MODE_CLASS_NAME}`, this.rootDarkTheme), 0);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Inject custom theme styles (only for internal testing)
153
+ */
154
+ __injectCustomThemeForTesting(selector, theme) {
155
+ if (theme != null) {
156
+ this.insert(buildTheme(selector, theme), 0);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Delete the requested rule from the stylesheet
162
+ */
163
+ delete(rule) {
164
+ // Get the index of this rule
165
+ const index = this.rules.indexOf(rule);
166
+ (0, _invariant.default)(index >= 0, "Couldn't find the index for rule %s", rule);
167
+
168
+ // Remove the rule from our index
169
+ this.rules.splice(index, 1);
170
+ if (this.isHeadless()) {
171
+ return;
172
+ }
173
+ const tag = this.getTag();
174
+ const sheet = tag.sheet;
175
+ (0, _invariant.default)(sheet, 'expected sheet');
176
+ sheet.deleteRule(index);
177
+ }
178
+
179
+ /**
180
+ *
181
+ */
182
+ normalizeRule(rule) {
183
+ const {
184
+ rootTheme
185
+ } = this;
186
+ if (this.supportsVariables || rootTheme == null) {
187
+ return rule;
188
+ }
189
+ return rule.replace(VARIABLE_MATCH, (_match, name) => {
190
+ return rootTheme[name];
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Get the rule position a rule should be inserted at according to the
196
+ * specified priority.
197
+ */
198
+ getInsertPositionForPriority(priority) {
199
+ // If there's an end rule associated with this priority, then get the next
200
+ // rule which will belong to the next priority
201
+ // The rule will be inserted before it and assigned to the current priority
202
+ const priorityRule = this.ruleForPriority.get(priority);
203
+ if (priorityRule != null) {
204
+ return this.rules.indexOf(priorityRule) + 1;
205
+ }
206
+
207
+ // If we've never created this priority before, then let's find the highest
208
+ // priority to target
209
+ const priorities = Array.from(this.ruleForPriority.keys()).sort((a, b) => b - a).filter(num => num > priority ? 1 : 0);
210
+
211
+ // If there's no priorities then place us at the start
212
+ if (priorities.length === 0) {
213
+ return this.getRuleCount();
214
+ }
215
+
216
+ // Place us next to the next highest priority
217
+ const lastPriority = priorities.pop();
218
+ return this.rules.indexOf(this.ruleForPriority.get(lastPriority));
219
+ }
220
+
221
+ /**
222
+ * Insert a rule into the stylesheet.
223
+ */
224
+ insert(rawLTRRule, priority, rawRTLRule) {
225
+ // Inject the stylesheet if it hasn't already been
226
+ if (this.injected === false) {
227
+ this.inject();
228
+ }
229
+ if (rawRTLRule != null) {
230
+ this.insert(addAncestorSelector(rawLTRRule, "html:not([dir='rtl'])"), priority);
231
+ this.insert(addAncestorSelector(rawRTLRule, "html[dir='rtl']"), priority);
232
+ return;
233
+ }
234
+ const rawRule = rawLTRRule;
235
+
236
+ // Don't insert this rule if it already exists
237
+ if (this.rules.includes(rawRule)) {
238
+ return;
239
+ }
240
+ const rule = this.normalizeRule(rawRule);
241
+
242
+ // Get the position where we should insert the rule
243
+ const insertPos = this.getInsertPositionForPriority(priority);
244
+ this.rules.splice(insertPos, 0, rule);
245
+
246
+ // Set this rule as the end of the priority group
247
+ this.ruleForPriority.set(priority, rule);
248
+ if (this.isHeadless()) {
249
+ return;
250
+ }
251
+ const tag = this.getTag();
252
+ const sheet = tag.sheet;
253
+ if (sheet != null) {
254
+ try {
255
+ sheet.insertRule(rule, insertPos);
256
+ } catch {
257
+ // Ignore: error likely due to inserting prefixed rules (e.g. `::-moz-range-thumb`).
258
+ }
259
+ }
260
+ // Ignore the case where sheet == null. It's an edge-case Edge 17 bug.
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Adds an ancestor selector in a media-query-aware way.
266
+ */
267
+ exports.StyleXSheet = StyleXSheet;
268
+ _defineProperty(StyleXSheet, "LIGHT_MODE_CLASS_NAME", LIGHT_MODE_CLASS_NAME);
269
+ _defineProperty(StyleXSheet, "DARK_MODE_CLASS_NAME", DARK_MODE_CLASS_NAME);
270
+ function addAncestorSelector(selector, ancestorSelector) {
271
+ if (!selector.startsWith('@')) {
272
+ return `${ancestorSelector} ${selector}`;
273
+ }
274
+ const firstBracketIndex = selector.indexOf('{');
275
+ const mediaQueryPart = selector.slice(0, firstBracketIndex + 1);
276
+ const rest = selector.slice(firstBracketIndex + 1);
277
+ return `${mediaQueryPart}${ancestorSelector} ${rest}`;
278
+ }
279
+ const styleSheet = new StyleXSheet({
280
+ supportsVariables: true,
281
+ rootTheme: {},
282
+ rootDarkTheme: {}
283
+ });
284
+ exports.styleSheet = styleSheet;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ 'use strict';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = inject;
7
+ var _StyleXSheet = require("./StyleXSheet");
8
+ /**
9
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ *
14
+ *
15
+ */
16
+
17
+ function inject(ltrRule, priority) {
18
+ let rtlRule = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
19
+ _StyleXSheet.styleSheet.insert(ltrRule, priority, rtlRule);
20
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { $ReadOnly } from 'utility-types';
9
+
10
+ // Simulating an opaque type
11
+ declare const classNameSymbol: unique symbol;
12
+
13
+ export type ClassNameFor<K, V> = string & {
14
+ _tag: typeof classNameSymbol;
15
+ _key: K;
16
+ _value: V;
17
+ };
18
+
19
+ export type ClassNameForKey<K> = ClassNameFor<K, string | number>;
20
+ export type ClassNameForValue<V> = ClassNameFor<string, V>;
21
+ export type ClassName = ClassNameFor<string, string | number>;
22
+
23
+ export type Namespace = {
24
+ readonly [K in string]: string | number | { [K in string]: string | number };
25
+ };
26
+
27
+ export type CompiledNamespace = {
28
+ readonly [K in string]:
29
+ | ClassNameFor<K, string | number>
30
+ | { [J in string]: ClassNameFor<`${K}+${J}`, string | number> };
31
+ };
32
+
33
+ export type NamespaceSet = {
34
+ readonly [K in string]: Namespace;
35
+ };
36
+
37
+ export type CompiledNamespaceSet = {
38
+ readonly [K in string]: CompiledNamespace;
39
+ };
40
+
41
+ type Stylex$Create = <S extends StyleXNamespaceSet>(
42
+ styles: S
43
+ ) => $ReadOnly<{
44
+ readonly [K in keyof S]: {
45
+ readonly [P in keyof S[K]]: S[K][P] extends string | number
46
+ ? StyleXClassNameFor<P, S[K][P]>
47
+ : {
48
+ readonly [F in keyof S[K][P]]: StyleXClassNameFor<
49
+ `${P}+${F}`,
50
+ S[K][P][F]
51
+ >;
52
+ };
53
+ };
54
+ }>;
55
+
56
+ type StylexInclude = <S extends CompiledNamespace>(
57
+ compiledNamespace: S
58
+ ) => {
59
+ readonly [K in keyof S]: S[K] extends ClassNameFor<K, infer V>
60
+ ? V
61
+ : Uncompiled<S[K]>;
62
+ };
63
+
64
+ type Stylex$Keyframes = <S extends StyleXNamespaceSet>(
65
+ animationConfig: S
66
+ ) => string;
67
+
68
+ type stylex = {
69
+ (
70
+ ...styles: ReadonlyArray<
71
+ StyleXArray<(CompiledNamespace | null | undefined) | boolean>
72
+ >
73
+ ): string;
74
+ create: Stylex$Create;
75
+ include: StylexInclude;
76
+ inject: (
77
+ ltrRule: string,
78
+ priority: number,
79
+ rtlRule: string | null | undefined
80
+ ) => void;
81
+ keyframes: Stylex$Keyframes;
82
+ };
83
+
84
+ export default stylex;
package/lib/stylex.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ Object.defineProperty(exports, "__esModule", {
13
+ value: true
14
+ });
15
+ exports.default = void 0;
16
+ var _stylexInject = _interopRequireDefault(require("./stylex-inject"));
17
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
+ const enableCache = true;
19
+ const cache = enableCache ? new WeakMap() : null;
20
+ function stylex() {
21
+ // Keep a set of property commits to the className
22
+ const definedProperties = [];
23
+ let className = '';
24
+ let nextCache = cache;
25
+ for (var _len = arguments.length, styles = new Array(_len), _key = 0; _key < _len; _key++) {
26
+ styles[_key] = arguments[_key];
27
+ }
28
+ while (styles.length) {
29
+ // Push nested styles back onto the stack to be processed
30
+ const possibleStyle = styles.pop();
31
+ if (Array.isArray(possibleStyle)) {
32
+ for (let i = 0; i < possibleStyle.length; i++) {
33
+ styles.push(possibleStyle[i]);
34
+ }
35
+ continue;
36
+ }
37
+
38
+ // Process an individual style object
39
+ const styleObj = possibleStyle;
40
+ if (styleObj != null && typeof styleObj === 'object') {
41
+ // Build up the class names defined by this object
42
+ let classNameChunk = '';
43
+ if (nextCache != null && nextCache.has(styleObj)) {
44
+ // Cache: read
45
+ const cacheEntry = nextCache.get(styleObj);
46
+ if (cacheEntry != null) {
47
+ classNameChunk = cacheEntry.classNameChunk;
48
+ definedProperties.push(...cacheEntry.definedPropertiesChunk);
49
+ nextCache = cacheEntry.next;
50
+ }
51
+ } else {
52
+ // Record the properties this object defines (and that haven't already
53
+ // been defined by later objects.)
54
+ const definedPropertiesChunk = [];
55
+ for (const prop in styleObj) {
56
+ const value = styleObj[prop];
57
+ // Style declarations, e.g., opacity: 's3fkgpd'
58
+ if (typeof value === 'string') {
59
+ // Skip adding to the chunks if property has already been seen
60
+ if (!definedProperties.includes(prop)) {
61
+ definedProperties.push(prop);
62
+ definedPropertiesChunk.push(prop);
63
+ classNameChunk += classNameChunk ? ' ' + value : value;
64
+ }
65
+ }
66
+ // Style conditions, e.g., ':hover', '@media', etc.
67
+ // TODO: remove if #98 is fixed
68
+ else if (typeof value === 'object') {
69
+ const condition = prop;
70
+ const nestedStyleObject = value;
71
+ for (const conditionalProp in nestedStyleObject) {
72
+ const conditionalValue = nestedStyleObject[conditionalProp];
73
+ const conditionalProperty = condition + conditionalProp;
74
+ // Skip if conditional property has already been seen
75
+ if (!definedProperties.includes(conditionalProperty)) {
76
+ definedProperties.push(conditionalProperty);
77
+ definedPropertiesChunk.push(conditionalProperty);
78
+ classNameChunk += classNameChunk ? ' ' + conditionalValue : conditionalValue;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ // Cache: write
84
+ if (nextCache != null) {
85
+ const emptyCache = new WeakMap();
86
+ nextCache.set(styleObj, {
87
+ classNameChunk,
88
+ definedPropertiesChunk,
89
+ next: emptyCache
90
+ });
91
+ nextCache = emptyCache;
92
+ }
93
+ }
94
+
95
+ // Order of classes in chunks matches property-iteration order of style
96
+ // object. Order of chunks matches passed order of styles from first to
97
+ // last (which we iterate over in reverse).
98
+ if (classNameChunk) {
99
+ className = className ? classNameChunk + ' ' + className : classNameChunk;
100
+ }
101
+ }
102
+ }
103
+ return className;
104
+ }
105
+ function stylexCreate(_styles) {
106
+ throw new Error('stylex.create should never be called. It should be compiled away.');
107
+ }
108
+ function stylexIncludes(_styles) {
109
+ throw new Error('stylex.extends should never be called. It should be compiled away.');
110
+ }
111
+ stylex.create = stylexCreate;
112
+ stylex.include = stylexIncludes;
113
+ stylex.keyframes = _keyframes => {
114
+ throw new Error('stylex.keyframes should never be called');
115
+ };
116
+ stylex.firstThatWorks = function () {
117
+ throw new Error('stylex.firstThatWorks should never be called.');
118
+ };
119
+ stylex.inject = _stylexInject.default;
120
+ stylex.UNSUPPORTED_PROPERTY = props => {
121
+ throw new Error('stylex.UNSUPPORTED_PROPERTY should never be called. It should be compiled away.');
122
+ };
123
+ var _default = stylex;
124
+ exports.default = _default;
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@stylexjs/stylex",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "A minimal runtime styling library for web.",
5
+ "main": "lib/stylex.js",
6
+ "types": "lib/stylex.d.ts",
7
+ "repository": "https://www.github.com/facebookexternal/stylex",
8
+ "author": "Naman Goel <nmn@fb.com>",
9
+ "license": "MIT",
10
+ "scripts": {
11
+ "build": "babel src/ --out-dir lib/ --copy-files",
12
+ "build-haste": "HASTE=true babel src/ --out-dir lib/ --copy-files",
13
+ "test": "jest"
14
+ },
15
+ "dependencies": {
16
+ "invariant": "^2.2.4",
17
+ "utility-types": "^3.10.0"
18
+ },
19
+ "devDependencies": {
20
+ "benchmark": "^2.1.4"
21
+ },
22
+ "jest": {}
23
+ }