@stylexjs/atoms 0.19.0

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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @stylexjs/atoms
2
+
3
+ Compile-time helpers for authoring StyleX atomic styles.
4
+
5
+ This package exposes CSS properties as a namespaced object and lets you express
6
+ static and dynamic styles using normal JavaScript syntax. There is no new
7
+ runtime styling system and no design tokens — everything compiles to the same
8
+ output as stylex.create.
9
+
10
+ The compiler treats atomic styles from this package as if they were authored
11
+ locally, enabling the same optimizations as normal StyleX styles.
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import * as stylex from '@stylexjs/stylex';
17
+ import x from '@stylexjs/atoms';
18
+
19
+ function Example({ color }) {
20
+ return (
21
+ <div
22
+ {...stylex.props(
23
+ x.display.flex,
24
+ x.flexDirection.column,
25
+ x.padding._16px,
26
+ x.width['calc(100% - 20cqi)'],
27
+ x.color(color),
28
+ )}
29
+ />
30
+ );
31
+ }
32
+ ```
33
+
34
+ ### Static values
35
+
36
+ Static styles are expressed via property access and are fully resolved at
37
+ compile time.
38
+
39
+ ```js
40
+ x.display.flex;
41
+ x.flexDirection.column;
42
+ ```
43
+
44
+ #### Values starting with numbers
45
+
46
+ Use a leading underscore for values that begin with a number. The underscore is
47
+ ignored by the compiler and has no semantic meaning.
48
+
49
+ ```js
50
+ x.padding._16px;
51
+ x.fontSize._1rem;
52
+ ```
53
+
54
+ ### Complex literal values
55
+
56
+ For values that are not valid JavaScript identifiers (for example, values that
57
+ contain spaces or symbols), use computed property syntax.
58
+
59
+ ```js
60
+ x.fontSize['1.25rem'];
61
+ x.width['calc(100% - 20cqi)'];
62
+ x.gridTemplateColumns['1fr minmax(0, 3fr)'];
63
+ ```
64
+
65
+ ### Dynamic values
66
+
67
+ Dynamic styles use call syntax and should be used sparingly.
68
+
69
+ ```js
70
+ x.color(color);
71
+ x.marginLeft(offset);
72
+ ```
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@stylexjs/atoms",
3
+ "version": "0.19.0",
4
+ "description": "Atomic style helpers for StyleX.",
5
+ "license": "MIT",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./babel-transform": "./src/babel-transform.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/facebook/stylex.git"
15
+ },
16
+ "scripts": {
17
+ "prebuild": "gen-types -i src/ -o src/"
18
+ },
19
+ "sideEffects": false,
20
+ "peerDependencies": {
21
+ "@stylexjs/stylex": "0.19.0"
22
+ },
23
+ "devDependencies": {
24
+ "scripts": "0.19.0"
25
+ },
26
+ "files": [
27
+ "src"
28
+ ]
29
+ }
@@ -0,0 +1,41 @@
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
+ export interface CompileUtils {
9
+ styleXCreateSet: (input: unknown, options: unknown) => [unknown, unknown];
10
+ convertObjectToAST: (obj: unknown) => unknown;
11
+ hoistExpression: (path: unknown, expr: unknown) => unknown;
12
+ injectDevClassNames: (
13
+ styles: unknown,
14
+ name: unknown,
15
+ state: unknown,
16
+ ) => unknown;
17
+ }
18
+
19
+ export declare function createUtilityStylesVisitor(
20
+ state: unknown,
21
+ compile: CompileUtils,
22
+ ): {
23
+ MemberExpression(path: unknown): void;
24
+ CallExpression(path: unknown): void;
25
+ };
26
+
27
+ export declare function isUtilityStylesIdentifier(
28
+ ident: unknown,
29
+ state: unknown,
30
+ path: unknown,
31
+ ): boolean;
32
+
33
+ export declare function getStaticStyleFromPath(
34
+ path: unknown,
35
+ state: unknown,
36
+ ): { property: string; value: string | number } | null;
37
+
38
+ export declare function getDynamicStyleFromPath(
39
+ path: unknown,
40
+ state: unknown,
41
+ ): { property: string; value: unknown } | null;
@@ -0,0 +1,341 @@
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
+ 'use strict';
9
+
10
+ const t = require('@babel/types');
11
+
12
+ const ATOMS_SOURCE = '@stylexjs/atoms';
13
+
14
+ /**
15
+ * Creates a visitor that transforms utility style expressions into compiled style objects.
16
+ *
17
+ * - css.display.flex -> { $$css: true, display: 'xabc123' }
18
+ * - css.color(myVar) -> _styles.color(myVar)
19
+ *
20
+ * @param {object} state - StateManager from babel-plugin
21
+ * @param {object} compile - Compilation utilities from babel-plugin
22
+ * @param {function} compile.styleXCreateSet - Compiles raw styles to class names + CSS
23
+ * @param {function} compile.convertObjectToAST - Converts JS object to babel AST
24
+ * @param {function} compile.hoistExpression - Hoists an expression to module scope
25
+ * @param {function} compile.injectDevClassNames - Adds dev class names
26
+ * @returns {object} Babel visitor
27
+ */
28
+ function createUtilityStylesVisitor(state, compile) {
29
+ return {
30
+ MemberExpression(path) {
31
+ if (path.node._utilityStyleProcessed) {
32
+ return;
33
+ }
34
+
35
+ const staticStyle = getStaticStyleFromPath(path, state);
36
+ if (staticStyle != null) {
37
+ path.node._utilityStyleProcessed = true;
38
+ compileStaticStyle(path, staticStyle, state, compile);
39
+ }
40
+ },
41
+
42
+ CallExpression(path) {
43
+ if (path.node._utilityStyleProcessed) {
44
+ return;
45
+ }
46
+
47
+ const dynamicStyle = getDynamicStyleFromPath(path, state);
48
+ if (dynamicStyle != null) {
49
+ path.node._utilityStyleProcessed = true;
50
+ compileDynamicStyle(path, dynamicStyle, state, compile);
51
+ }
52
+ },
53
+ };
54
+ }
55
+
56
+ function isUtilityStylesIdentifier(ident, state, path) {
57
+ if (state.atomImports && state.atomImports.has(ident.name)) {
58
+ return true;
59
+ }
60
+
61
+ const binding = path.scope?.getBinding(ident.name);
62
+ if (!binding) {
63
+ return false;
64
+ }
65
+
66
+ if (
67
+ binding.path.isImportSpecifier() &&
68
+ binding.path.parent.type === 'ImportDeclaration' &&
69
+ binding.path.parent.source.value === ATOMS_SOURCE
70
+ ) {
71
+ return true;
72
+ }
73
+
74
+ if (
75
+ binding.path.isImportNamespaceSpecifier() &&
76
+ binding.path.parent.type === 'ImportDeclaration' &&
77
+ binding.path.parent.source.value === ATOMS_SOURCE
78
+ ) {
79
+ return true;
80
+ }
81
+
82
+ if (
83
+ binding.path.isImportDefaultSpecifier() &&
84
+ binding.path.parent.type === 'ImportDeclaration' &&
85
+ binding.path.parent.source.value === ATOMS_SOURCE
86
+ ) {
87
+ return true;
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ function getPropKey(prop, computed) {
94
+ if (!computed && t.isIdentifier(prop)) {
95
+ return prop.name;
96
+ }
97
+ if (computed && t.isStringLiteral(prop)) {
98
+ return prop.value;
99
+ }
100
+ if (computed && t.isNumericLiteral(prop)) {
101
+ return String(prop.value);
102
+ }
103
+ return null;
104
+ }
105
+
106
+ /**
107
+ * Strips a leading underscore from CSS values. This allows using underscore-
108
+ * prefixed identifiers for CSS values that conflict with JS reserved words
109
+ * or start with a digit (e.g., css.display._flex or css.zIndex._1).
110
+ */
111
+ function normalizeValue(value) {
112
+ if (typeof value === 'string' && value.startsWith('_')) {
113
+ return value.slice(1);
114
+ }
115
+ return value;
116
+ }
117
+
118
+ function getStaticStyleFromPath(path, state) {
119
+ const node = path.node;
120
+ if (!t.isMemberExpression(node)) {
121
+ return null;
122
+ }
123
+
124
+ if (
125
+ path.parentPath?.isCallExpression() &&
126
+ path.parentPath.node.callee === node
127
+ ) {
128
+ return null;
129
+ }
130
+
131
+ const valueKey = getPropKey(node.property, node.computed);
132
+ if (valueKey == null) {
133
+ return null;
134
+ }
135
+
136
+ const parent = node.object;
137
+
138
+ if (t.isMemberExpression(parent)) {
139
+ const propName = getPropKey(parent.property, parent.computed);
140
+ const base = parent.object;
141
+ if (
142
+ propName != null &&
143
+ t.isIdentifier(base) &&
144
+ isUtilityStylesIdentifier(base, state, path)
145
+ ) {
146
+ return { property: propName, value: normalizeValue(valueKey) };
147
+ }
148
+ }
149
+
150
+ if (
151
+ t.isIdentifier(parent) &&
152
+ isUtilityStylesIdentifier(parent, state, path)
153
+ ) {
154
+ const importedName = state.atomImports?.get(parent.name);
155
+ if (importedName == null) {
156
+ return null;
157
+ }
158
+ const property = importedName === '*' ? valueKey : importedName;
159
+ return { property, value: normalizeValue(valueKey) };
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ function getDynamicStyleFromPath(path, state) {
166
+ const callee = path.get('callee');
167
+ if (!callee.isMemberExpression()) {
168
+ return null;
169
+ }
170
+
171
+ const valueKey = getPropKey(callee.node.property, callee.node.computed);
172
+ if (valueKey == null) {
173
+ return null;
174
+ }
175
+
176
+ if (path.node.arguments.length !== 1) {
177
+ return null;
178
+ }
179
+
180
+ const argPath = path.get('arguments')[0];
181
+ if (!argPath || !argPath.node || !argPath.isExpression()) {
182
+ return null;
183
+ }
184
+
185
+ const parent = callee.node.object;
186
+
187
+ if (
188
+ t.isIdentifier(parent) &&
189
+ isUtilityStylesIdentifier(parent, state, path)
190
+ ) {
191
+ return {
192
+ property: valueKey,
193
+ value: argPath.node,
194
+ };
195
+ }
196
+
197
+ if (t.isMemberExpression(parent)) {
198
+ const propName = getPropKey(parent.property, parent.computed);
199
+ const base = parent.object;
200
+ if (
201
+ propName != null &&
202
+ t.isIdentifier(base) &&
203
+ isUtilityStylesIdentifier(base, state, path)
204
+ ) {
205
+ return {
206
+ property: propName,
207
+ value: argPath.node,
208
+ };
209
+ }
210
+ }
211
+
212
+ return null;
213
+ }
214
+
215
+ /**
216
+ * Compile static utility style directly to a compiled style object.
217
+ * css.display.flex -> { $$css: true, display: 'xabc123' }
218
+ */
219
+ function compileStaticStyle(path, styleInfo, state, compile) {
220
+ const { property, value } = styleInfo;
221
+ const rawStyle = { [property]: value };
222
+ const styleInput = { __inline__: rawStyle };
223
+
224
+ // eslint-disable-next-line prefer-const
225
+ let [compiledStyles, injectedStyles] = compile.styleXCreateSet(
226
+ styleInput,
227
+ state.options,
228
+ );
229
+
230
+ if (state.isDev && state.options.enableDevClassNames) {
231
+ compiledStyles = {
232
+ ...compile.injectDevClassNames(compiledStyles, null, state),
233
+ };
234
+ }
235
+
236
+ const listOfStyles = Object.entries(injectedStyles).map(
237
+ ([key, { priority, ...rest }]) => [key, rest, priority],
238
+ );
239
+ state.registerStyles(listOfStyles, path);
240
+
241
+ const compiledStyle = compiledStyles.__inline__;
242
+ if (compiledStyle == null) {
243
+ return;
244
+ }
245
+
246
+ const compiledAst = compile.convertObjectToAST(compiledStyle);
247
+ path.replaceWith(compiledAst);
248
+ }
249
+
250
+ /**
251
+ * Compile dynamic utility style to a hoisted function pattern.
252
+ * css.color(myVar) -> _hoisted.color(myVar)
253
+ */
254
+ function compileDynamicStyle(path, styleInfo, state, compile) {
255
+ const { property, value } = styleInfo;
256
+ const varName = `--x-${property}`;
257
+ const rawStyle = { [property]: `var(${varName})` };
258
+ const styleInput = { __inline__: rawStyle };
259
+
260
+ // eslint-disable-next-line prefer-const
261
+ let [compiledStyles, injectedStyles] = compile.styleXCreateSet(
262
+ styleInput,
263
+ state.options,
264
+ );
265
+
266
+ if (state.isDev && state.options.enableDevClassNames) {
267
+ compiledStyles = {
268
+ ...compile.injectDevClassNames(compiledStyles, null, state),
269
+ };
270
+ }
271
+
272
+ injectedStyles[varName] = {
273
+ priority: 0,
274
+ ltr: `@property ${varName} { syntax: "*"; inherits: false;}`,
275
+ rtl: null,
276
+ };
277
+
278
+ const listOfStyles = Object.entries(injectedStyles).map(
279
+ ([key, { priority, ...rest }]) => [key, rest, priority],
280
+ );
281
+ state.registerStyles(listOfStyles, path);
282
+
283
+ const compiledStyle = compiledStyles.__inline__;
284
+ if (compiledStyle == null) {
285
+ return;
286
+ }
287
+
288
+ // compiledStyle has: { $$css: true, "devClassName": "devClassName", "prop-hash": "xAbc123" }
289
+ // We need the prop-hash key (not $$css, not the dev class name where key === value).
290
+ const propKey = Object.keys(compiledStyle).find(
291
+ (k) => k !== '$$css' && compiledStyle[k] !== k,
292
+ );
293
+ const className = propKey != null ? compiledStyle[propKey] : null;
294
+ if (className == null || propKey == null || typeof className !== 'string') {
295
+ return;
296
+ }
297
+
298
+ const param = t.identifier('_v');
299
+ const nullCheck = t.binaryExpression('!=', param, t.nullLiteral());
300
+
301
+ const compiledObj = t.objectExpression([
302
+ t.objectProperty(
303
+ t.stringLiteral(propKey),
304
+ t.conditionalExpression(nullCheck, t.stringLiteral(className), param),
305
+ ),
306
+ t.objectProperty(t.stringLiteral('$$css'), t.booleanLiteral(true)),
307
+ ]);
308
+
309
+ const inlineVarsObj = t.objectExpression([
310
+ t.objectProperty(
311
+ t.stringLiteral(varName),
312
+ t.conditionalExpression(
313
+ t.binaryExpression('!=', param, t.nullLiteral()),
314
+ param,
315
+ t.identifier('undefined'),
316
+ ),
317
+ ),
318
+ ]);
319
+
320
+ const fnBody = t.arrayExpression([compiledObj, inlineVarsObj]);
321
+ const arrowFn = t.arrowFunctionExpression([param], fnBody);
322
+
323
+ const stylesObj = t.objectExpression([
324
+ t.objectProperty(t.identifier(property), arrowFn),
325
+ ]);
326
+
327
+ const hoistedId = compile.hoistExpression(path, stylesObj);
328
+
329
+ const callExpr = t.callExpression(
330
+ t.memberExpression(hoistedId, t.identifier(property)),
331
+ [value],
332
+ );
333
+ path.replaceWith(callExpr);
334
+ }
335
+
336
+ module.exports = {
337
+ createUtilityStylesVisitor,
338
+ isUtilityStylesIdentifier,
339
+ getStaticStyleFromPath,
340
+ getDynamicStyleFromPath,
341
+ };
@@ -0,0 +1,48 @@
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
+ import type { NodePath } from '@babel/traverse';
11
+
12
+ type StyleInfo = Readonly<{ property: string, value: string | number }>;
13
+ type DynamicStyleInfo = Readonly<{ property: string, value: unknown }>;
14
+
15
+ type CompileUtils = Readonly<{ +[string]: unknown }>;
16
+
17
+ type BabelVisitor = Readonly<{
18
+ MemberExpression(path: NodePath<>): void,
19
+ CallExpression(path: NodePath<>): void,
20
+ }>;
21
+
22
+ declare function createUtilityStylesVisitor(
23
+ state: unknown,
24
+ compile: CompileUtils,
25
+ ): BabelVisitor;
26
+
27
+ declare function isUtilityStylesIdentifier(
28
+ ident: unknown,
29
+ state: unknown,
30
+ path: NodePath<>,
31
+ ): boolean;
32
+
33
+ declare function getStaticStyleFromPath(
34
+ path: NodePath<>,
35
+ state: unknown,
36
+ ): ?StyleInfo;
37
+
38
+ declare function getDynamicStyleFromPath(
39
+ path: NodePath<>,
40
+ state: unknown,
41
+ ): ?DynamicStyleInfo;
42
+
43
+ declare module.exports: {
44
+ createUtilityStylesVisitor: typeof createUtilityStylesVisitor,
45
+ isUtilityStylesIdentifier: typeof isUtilityStylesIdentifier,
46
+ getStaticStyleFromPath: typeof getStaticStyleFromPath,
47
+ getDynamicStyleFromPath: typeof getDynamicStyleFromPath,
48
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,18 @@
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
+ import type { CSSProperties, StyleXStyles } from '@stylexjs/stylex';
11
+ export type Atom = StyleXStyles<{ readonly [$$Key$$: string]: unknown }>;
12
+ export type AtomProperty = {
13
+ readonly [$$Key$$: string]: Atom;
14
+ (value: string | number): Atom;
15
+ };
16
+ export type Atoms = { readonly [Key in keyof CSSProperties]-?: AtomProperty };
17
+ declare const atoms: Atoms;
18
+ export default atoms;
package/src/index.js ADDED
@@ -0,0 +1,32 @@
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
+ const errorMessage = (prop) =>
13
+ "'@stylexjs/atoms' must be compiled away by '@stylexjs/babel-plugin'. " +
14
+ `Attempted to access '${prop}' at runtime.`;
15
+
16
+ const _proxy = new Proxy(
17
+ {},
18
+ {
19
+ get(target, prop) {
20
+ if (typeof prop === 'symbol') {
21
+ return target[prop];
22
+ }
23
+ if (prop === 'default' || prop === '__esModule') {
24
+ return target[prop];
25
+ }
26
+ throw new Error(errorMessage(prop));
27
+ },
28
+ },
29
+ );
30
+
31
+ module.exports = _proxy;
32
+ module.exports.default = _proxy;
@@ -0,0 +1,25 @@
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
+ import type { CSSProperties, StyleXStyles } from '@stylexjs/stylex';
11
+
12
+ export type Atom = StyleXStyles<{ +[string]: mixed }>;
13
+
14
+ export type AtomProperty = {
15
+ +[string]: Atom,
16
+ (value: string | number): Atom,
17
+ };
18
+
19
+ export type Atoms = Readonly<{
20
+ [_Key in keyof CSSProperties]: AtomProperty,
21
+ }>;
22
+
23
+ declare const atoms: Atoms;
24
+
25
+ declare export default typeof atoms;