@idealyst/theme 1.1.7 → 1.1.8
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/package.json +29 -1
- package/src/babel/index.ts +9 -0
- package/src/babel/plugin.js +871 -0
- package/src/babel/plugin.ts +187 -0
- package/src/babel/runtime.ts +94 -0
- package/src/babel/theme-analyzer.js +357 -0
- package/src/componentStyles.ts +93 -0
- package/src/config/cli.ts +95 -0
- package/src/config/generator.ts +817 -0
- package/src/config/index.ts +10 -0
- package/src/config/types.ts +112 -0
- package/src/darkTheme.ts +14 -14
- package/src/extensions.ts +110 -0
- package/src/index.ts +16 -4
- package/src/styleBuilder.ts +108 -0
- package/src/theme/extensions.ts +7 -0
- package/src/unistyles.ts +5 -15
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idealyst StyleBuilder Babel Plugin
|
|
3
|
+
*
|
|
4
|
+
* Transforms defineStyle/extendStyle calls to StyleSheet.create with $iterator expansion.
|
|
5
|
+
* All merging happens at BUILD TIME so Unistyles can properly trace theme dependencies.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: For extensions to work, the extension file must be imported BEFORE
|
|
8
|
+
* the components that use defineStyle. Example:
|
|
9
|
+
*
|
|
10
|
+
* // App.tsx
|
|
11
|
+
* import './style-extensions'; // FIRST - registers extensions
|
|
12
|
+
* import { Text } from '@idealyst/components'; // SECOND - uses extensions
|
|
13
|
+
*
|
|
14
|
+
* Transformation:
|
|
15
|
+
* defineStyle('Button', (theme) => ({
|
|
16
|
+
* button: { backgroundColor: theme.$intents.primary }
|
|
17
|
+
* }))
|
|
18
|
+
*
|
|
19
|
+
* → StyleSheet.create((theme) => ({
|
|
20
|
+
* button: {
|
|
21
|
+
* variants: {
|
|
22
|
+
* intent: {
|
|
23
|
+
* primary: { backgroundColor: theme.intents.primary.primary },
|
|
24
|
+
* success: { backgroundColor: theme.intents.success.primary },
|
|
25
|
+
* ...
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* }))
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const { loadThemeKeys } = require('./theme-analyzer');
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Global Extension Registry - Persists across files during build
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
// Map of componentName -> { base: AST | null, extensions: AST[], overrides: AST | null }
|
|
39
|
+
const styleRegistry = {};
|
|
40
|
+
|
|
41
|
+
function getOrCreateEntry(componentName) {
|
|
42
|
+
if (!styleRegistry[componentName]) {
|
|
43
|
+
styleRegistry[componentName] = {
|
|
44
|
+
base: null,
|
|
45
|
+
extensions: [],
|
|
46
|
+
override: null,
|
|
47
|
+
themeParam: 'theme',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return styleRegistry[componentName];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// AST Deep Merge - Merges style object ASTs at build time
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deep merge two ObjectExpression ASTs.
|
|
59
|
+
* Source properties override target properties.
|
|
60
|
+
* Nested objects are recursively merged.
|
|
61
|
+
*/
|
|
62
|
+
function mergeObjectExpressions(t, target, source) {
|
|
63
|
+
if (!t.isObjectExpression(target) || !t.isObjectExpression(source)) {
|
|
64
|
+
// If source is not an object, it replaces target entirely
|
|
65
|
+
return source;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const targetProps = new Map();
|
|
69
|
+
for (const prop of target.properties) {
|
|
70
|
+
if (t.isObjectProperty(prop)) {
|
|
71
|
+
const key = t.isIdentifier(prop.key) ? prop.key.name :
|
|
72
|
+
t.isStringLiteral(prop.key) ? prop.key.value : null;
|
|
73
|
+
if (key) {
|
|
74
|
+
targetProps.set(key, prop);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const resultProps = [...target.properties];
|
|
80
|
+
|
|
81
|
+
for (const prop of source.properties) {
|
|
82
|
+
if (!t.isObjectProperty(prop)) continue;
|
|
83
|
+
|
|
84
|
+
const key = t.isIdentifier(prop.key) ? prop.key.name :
|
|
85
|
+
t.isStringLiteral(prop.key) ? prop.key.value : null;
|
|
86
|
+
if (!key) continue;
|
|
87
|
+
|
|
88
|
+
const existingProp = targetProps.get(key);
|
|
89
|
+
|
|
90
|
+
if (existingProp) {
|
|
91
|
+
// Both have this property - need to merge or replace
|
|
92
|
+
const existingValue = existingProp.value;
|
|
93
|
+
const newValue = prop.value;
|
|
94
|
+
|
|
95
|
+
// If both are objects, deep merge
|
|
96
|
+
if (t.isObjectExpression(existingValue) && t.isObjectExpression(newValue)) {
|
|
97
|
+
const mergedValue = mergeObjectExpressions(t, existingValue, newValue);
|
|
98
|
+
// Replace the existing prop's value
|
|
99
|
+
const idx = resultProps.indexOf(existingProp);
|
|
100
|
+
resultProps[idx] = t.objectProperty(existingProp.key, mergedValue);
|
|
101
|
+
}
|
|
102
|
+
// If both are arrow functions (dynamic styles), merge their return values
|
|
103
|
+
else if (t.isArrowFunctionExpression(existingValue) && t.isArrowFunctionExpression(newValue)) {
|
|
104
|
+
const mergedFn = mergeDynamicStyleFunctions(t, existingValue, newValue);
|
|
105
|
+
const idx = resultProps.indexOf(existingProp);
|
|
106
|
+
resultProps[idx] = t.objectProperty(existingProp.key, mergedFn);
|
|
107
|
+
}
|
|
108
|
+
// Otherwise, source replaces target
|
|
109
|
+
else {
|
|
110
|
+
const idx = resultProps.indexOf(existingProp);
|
|
111
|
+
resultProps[idx] = prop;
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// New property from source
|
|
115
|
+
resultProps.push(prop);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return t.objectExpression(resultProps);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Merge two dynamic style functions (arrow functions that return style objects).
|
|
124
|
+
* Creates a new function that merges both return values.
|
|
125
|
+
*/
|
|
126
|
+
function mergeDynamicStyleFunctions(t, baseFn, extFn) {
|
|
127
|
+
// Get the bodies (assuming they return ObjectExpressions)
|
|
128
|
+
let baseBody = baseFn.body;
|
|
129
|
+
let extBody = extFn.body;
|
|
130
|
+
|
|
131
|
+
// Handle parenthesized expressions
|
|
132
|
+
if (t.isParenthesizedExpression(baseBody)) {
|
|
133
|
+
baseBody = baseBody.expression;
|
|
134
|
+
}
|
|
135
|
+
if (t.isParenthesizedExpression(extBody)) {
|
|
136
|
+
extBody = extBody.expression;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If both return ObjectExpressions directly, merge them
|
|
140
|
+
if (t.isObjectExpression(baseBody) && t.isObjectExpression(extBody)) {
|
|
141
|
+
const mergedBody = mergeObjectExpressions(t, baseBody, extBody);
|
|
142
|
+
return t.arrowFunctionExpression(baseFn.params, mergedBody);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// For block statements, this is more complex - just use extension for now
|
|
146
|
+
return extFn;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Merge a callback body (the object returned by theme => ({ ... }))
|
|
151
|
+
*/
|
|
152
|
+
function mergeCallbackBodies(t, baseCallback, extCallback) {
|
|
153
|
+
let baseBody = baseCallback.body;
|
|
154
|
+
let extBody = extCallback.body;
|
|
155
|
+
|
|
156
|
+
// Handle parenthesized expressions
|
|
157
|
+
if (t.isParenthesizedExpression(baseBody)) {
|
|
158
|
+
baseBody = baseBody.expression;
|
|
159
|
+
}
|
|
160
|
+
if (t.isParenthesizedExpression(extBody)) {
|
|
161
|
+
extBody = extBody.expression;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (t.isObjectExpression(baseBody) && t.isObjectExpression(extBody)) {
|
|
165
|
+
const mergedBody = mergeObjectExpressions(t, baseBody, extBody);
|
|
166
|
+
return t.arrowFunctionExpression(baseCallback.params, mergedBody);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// If can't merge, use base
|
|
170
|
+
return baseCallback;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// $iterator Expansion Logic
|
|
175
|
+
// ============================================================================
|
|
176
|
+
|
|
177
|
+
function expandIterators(t, callback, themeParam, keys, verbose, expandedVariants) {
|
|
178
|
+
const cloned = t.cloneDeep(callback);
|
|
179
|
+
|
|
180
|
+
function processNode(node, depth = 0) {
|
|
181
|
+
if (!node || typeof node !== 'object') return node;
|
|
182
|
+
|
|
183
|
+
if (Array.isArray(node)) {
|
|
184
|
+
return node.map(n => processNode(n, depth));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Look for variants: { intent: { ... }, size: { ... } }
|
|
188
|
+
if (t.isObjectProperty(node) && t.isIdentifier(node.key, { name: 'variants' })) {
|
|
189
|
+
if (t.isObjectExpression(node.value)) {
|
|
190
|
+
const expanded = expandVariantsObject(t, node.value, themeParam, keys, verbose, expandedVariants);
|
|
191
|
+
return t.objectProperty(node.key, expanded);
|
|
192
|
+
} else if (t.isTSAsExpression(node.value)) {
|
|
193
|
+
const innerValue = node.value.expression;
|
|
194
|
+
if (t.isObjectExpression(innerValue)) {
|
|
195
|
+
const expanded = expandVariantsObject(t, innerValue, themeParam, keys, verbose, expandedVariants);
|
|
196
|
+
return t.objectProperty(node.key, t.tsAsExpression(expanded, node.value.typeAnnotation));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (t.isObjectExpression(node)) {
|
|
202
|
+
return t.objectExpression(
|
|
203
|
+
node.properties.map(prop => processNode(prop, depth + 1))
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (t.isObjectProperty(node)) {
|
|
208
|
+
return t.objectProperty(
|
|
209
|
+
node.key,
|
|
210
|
+
processNode(node.value, depth + 1),
|
|
211
|
+
node.computed,
|
|
212
|
+
node.shorthand
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
|
|
217
|
+
const processedBody = processNode(node.body, depth + 1);
|
|
218
|
+
return t.arrowFunctionExpression(
|
|
219
|
+
node.params,
|
|
220
|
+
processedBody
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Handle TSAsExpression (e.g., { ... } as const)
|
|
225
|
+
if (t.isTSAsExpression(node)) {
|
|
226
|
+
const processedExpr = processNode(node.expression, depth + 1);
|
|
227
|
+
return t.tsAsExpression(processedExpr, node.typeAnnotation);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle ParenthesizedExpression
|
|
231
|
+
if (t.isParenthesizedExpression(node)) {
|
|
232
|
+
const processedExpr = processNode(node.expression, depth + 1);
|
|
233
|
+
return t.parenthesizedExpression(processedExpr);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Handle BlockStatement (for nested arrow functions with block bodies)
|
|
237
|
+
if (t.isBlockStatement(node)) {
|
|
238
|
+
const newBody = node.body.map(stmt => {
|
|
239
|
+
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
240
|
+
return t.returnStatement(processNode(stmt.argument, depth + 1));
|
|
241
|
+
}
|
|
242
|
+
// Handle variable declarations that might contain style objects
|
|
243
|
+
if (t.isVariableDeclaration(stmt)) {
|
|
244
|
+
return t.variableDeclaration(
|
|
245
|
+
stmt.kind,
|
|
246
|
+
stmt.declarations.map(decl => {
|
|
247
|
+
if (decl.init) {
|
|
248
|
+
return t.variableDeclarator(decl.id, processNode(decl.init, depth + 1));
|
|
249
|
+
}
|
|
250
|
+
return decl;
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return stmt;
|
|
255
|
+
});
|
|
256
|
+
return t.blockStatement(newBody);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return node;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Handle the callback body - may be ObjectExpression, TSAsExpression, ParenthesizedExpression, or BlockStatement
|
|
263
|
+
let bodyToProcess = cloned.body;
|
|
264
|
+
|
|
265
|
+
// Unwrap ParenthesizedExpression
|
|
266
|
+
if (t.isParenthesizedExpression(bodyToProcess)) {
|
|
267
|
+
bodyToProcess = bodyToProcess.expression;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Unwrap TSAsExpression
|
|
271
|
+
if (t.isTSAsExpression(bodyToProcess)) {
|
|
272
|
+
const processedExpr = processNode(bodyToProcess.expression);
|
|
273
|
+
cloned.body = t.tsAsExpression(processedExpr, bodyToProcess.typeAnnotation);
|
|
274
|
+
} else if (t.isObjectExpression(bodyToProcess)) {
|
|
275
|
+
cloned.body = processNode(bodyToProcess);
|
|
276
|
+
} else if (t.isBlockStatement(cloned.body)) {
|
|
277
|
+
cloned.body.body = cloned.body.body.map(stmt => {
|
|
278
|
+
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
279
|
+
return t.returnStatement(processNode(stmt.argument));
|
|
280
|
+
}
|
|
281
|
+
return stmt;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return cloned;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function expandVariantsObject(t, variantsObj, themeParam, keys, verbose, expandedVariants) {
|
|
289
|
+
const newProperties = [];
|
|
290
|
+
|
|
291
|
+
verbose(` expandVariantsObject: processing ${variantsObj.properties?.length || 0} properties`);
|
|
292
|
+
|
|
293
|
+
for (const prop of variantsObj.properties) {
|
|
294
|
+
if (!t.isObjectProperty(prop)) {
|
|
295
|
+
newProperties.push(prop);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let variantName;
|
|
300
|
+
if (t.isIdentifier(prop.key)) {
|
|
301
|
+
variantName = prop.key.name;
|
|
302
|
+
} else if (t.isStringLiteral(prop.key)) {
|
|
303
|
+
variantName = prop.key.value;
|
|
304
|
+
} else {
|
|
305
|
+
newProperties.push(prop);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
verbose(` Checking variant: ${variantName}`);
|
|
310
|
+
const iteratorInfo = findIteratorPattern(t, prop.value, themeParam);
|
|
311
|
+
verbose(` Iterator info for ${variantName}: ${JSON.stringify(iteratorInfo)}`);
|
|
312
|
+
|
|
313
|
+
if (iteratorInfo) {
|
|
314
|
+
verbose(` Expanding ${variantName} variant with ${iteratorInfo.type} iterator`);
|
|
315
|
+
verbose(` Keys available: ${JSON.stringify(keys?.sizes?.[iteratorInfo.componentName] || keys?.intents || [])}`);
|
|
316
|
+
const expanded = expandVariantWithIterator(t, prop.value, themeParam, keys, iteratorInfo, verbose);
|
|
317
|
+
newProperties.push(t.objectProperty(prop.key, expanded));
|
|
318
|
+
|
|
319
|
+
const iteratorKey = iteratorInfo.componentName
|
|
320
|
+
? `${iteratorInfo.type}.${iteratorInfo.componentName}`
|
|
321
|
+
: iteratorInfo.type;
|
|
322
|
+
expandedVariants.push({ variant: variantName, iterator: iteratorKey });
|
|
323
|
+
} else {
|
|
324
|
+
newProperties.push(prop);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return t.objectExpression(newProperties);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function findIteratorPattern(t, node, themeParam, debugLog = () => {}) {
|
|
332
|
+
let result = null;
|
|
333
|
+
|
|
334
|
+
function walk(n, path = '') {
|
|
335
|
+
if (!n || typeof n !== 'object' || result) return;
|
|
336
|
+
|
|
337
|
+
if (t.isMemberExpression(n)) {
|
|
338
|
+
const chain = getMemberChain(t, n, themeParam);
|
|
339
|
+
if (chain) {
|
|
340
|
+
for (let i = 0; i < chain.length; i++) {
|
|
341
|
+
const part = chain[i];
|
|
342
|
+
if (part.startsWith('$')) {
|
|
343
|
+
const iteratorName = part.slice(1);
|
|
344
|
+
|
|
345
|
+
if (iteratorName === 'intents') {
|
|
346
|
+
result = {
|
|
347
|
+
type: 'intents',
|
|
348
|
+
accessPath: chain.slice(i + 1),
|
|
349
|
+
};
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (i > 0 && chain[i - 1] === 'sizes') {
|
|
354
|
+
result = {
|
|
355
|
+
type: 'sizes',
|
|
356
|
+
componentName: iteratorName,
|
|
357
|
+
accessPath: chain.slice(i + 1),
|
|
358
|
+
};
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const key of Object.keys(n)) {
|
|
367
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
|
|
368
|
+
if (n[key] && typeof n[key] === 'object') {
|
|
369
|
+
walk(n[key]);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
walk(node);
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getMemberChain(t, node, themeParam) {
|
|
379
|
+
const parts = [];
|
|
380
|
+
|
|
381
|
+
function walk(n) {
|
|
382
|
+
if (t.isIdentifier(n)) {
|
|
383
|
+
if (n.name === themeParam) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (t.isMemberExpression(n)) {
|
|
390
|
+
if (!walk(n.object)) return false;
|
|
391
|
+
|
|
392
|
+
if (t.isIdentifier(n.property)) {
|
|
393
|
+
parts.push(n.property.name);
|
|
394
|
+
} else if (t.isStringLiteral(n.property)) {
|
|
395
|
+
parts.push(n.property.value);
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return walk(node) ? parts : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function expandVariantWithIterator(t, valueNode, themeParam, keys, iteratorInfo, verbose) {
|
|
407
|
+
let keysToExpand = [];
|
|
408
|
+
|
|
409
|
+
if (iteratorInfo.type === 'intents') {
|
|
410
|
+
keysToExpand = keys?.intents || [];
|
|
411
|
+
} else if (iteratorInfo.type === 'sizes' && iteratorInfo.componentName) {
|
|
412
|
+
keysToExpand = keys?.sizes?.[iteratorInfo.componentName] || [];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (keysToExpand.length === 0) {
|
|
416
|
+
return valueNode;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
verbose(` Expanding to keys: ${keysToExpand.join(', ')}`);
|
|
420
|
+
|
|
421
|
+
const expandedProps = [];
|
|
422
|
+
|
|
423
|
+
for (const key of keysToExpand) {
|
|
424
|
+
const expandedValue = replaceIteratorRefs(t, valueNode, themeParam, iteratorInfo, key);
|
|
425
|
+
expandedProps.push(
|
|
426
|
+
t.objectProperty(
|
|
427
|
+
t.identifier(key),
|
|
428
|
+
expandedValue
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return t.objectExpression(expandedProps);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function replaceIteratorRefs(t, node, themeParam, iteratorInfo, key) {
|
|
437
|
+
if (!node || typeof node !== 'object') return node;
|
|
438
|
+
|
|
439
|
+
const cloned = t.cloneDeep(node);
|
|
440
|
+
|
|
441
|
+
function walk(n) {
|
|
442
|
+
if (!n || typeof n !== 'object') return n;
|
|
443
|
+
|
|
444
|
+
if (Array.isArray(n)) {
|
|
445
|
+
return n.map(walk);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (t.isMemberExpression(n)) {
|
|
449
|
+
const chain = getMemberChain(t, n, themeParam);
|
|
450
|
+
if (chain) {
|
|
451
|
+
let hasIterator = false;
|
|
452
|
+
let dollarIndex = -1;
|
|
453
|
+
|
|
454
|
+
for (let i = 0; i < chain.length; i++) {
|
|
455
|
+
if (chain[i].startsWith('$')) {
|
|
456
|
+
const iterName = chain[i].slice(1);
|
|
457
|
+
if (iteratorInfo.type === 'intents' && iterName === 'intents') {
|
|
458
|
+
hasIterator = true;
|
|
459
|
+
dollarIndex = i;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
if (iteratorInfo.type === 'sizes' && iterName === iteratorInfo.componentName && i > 0 && chain[i - 1] === 'sizes') {
|
|
463
|
+
hasIterator = true;
|
|
464
|
+
dollarIndex = i;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (hasIterator) {
|
|
471
|
+
const newChain = [];
|
|
472
|
+
for (let i = 0; i < chain.length; i++) {
|
|
473
|
+
if (i === dollarIndex) {
|
|
474
|
+
const iterName = chain[i].slice(1);
|
|
475
|
+
newChain.push(iterName);
|
|
476
|
+
newChain.push(key);
|
|
477
|
+
} else {
|
|
478
|
+
newChain.push(chain[i]);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return buildMemberExpression(t, themeParam, newChain);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (t.isObjectExpression(n)) {
|
|
487
|
+
return t.objectExpression(
|
|
488
|
+
n.properties.map(prop => walk(prop))
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (t.isObjectProperty(n)) {
|
|
493
|
+
return t.objectProperty(
|
|
494
|
+
n.key,
|
|
495
|
+
walk(n.value),
|
|
496
|
+
n.computed,
|
|
497
|
+
n.shorthand
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return n;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return walk(cloned);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function buildMemberExpression(t, base, chain) {
|
|
508
|
+
let expr = t.identifier(base);
|
|
509
|
+
for (const part of chain) {
|
|
510
|
+
expr = t.memberExpression(expr, t.identifier(part));
|
|
511
|
+
}
|
|
512
|
+
return expr;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ============================================================================
|
|
516
|
+
// Babel Plugin
|
|
517
|
+
// ============================================================================
|
|
518
|
+
|
|
519
|
+
module.exports = function idealystStylesPlugin({ types: t }) {
|
|
520
|
+
// Store babel types for use in extractThemeKeysFromAST
|
|
521
|
+
babelTypes = t;
|
|
522
|
+
|
|
523
|
+
let debugMode = false;
|
|
524
|
+
let verboseMode = false;
|
|
525
|
+
|
|
526
|
+
function debug(...args) {
|
|
527
|
+
if (debugMode || verboseMode) {
|
|
528
|
+
console.log('[idealyst-plugin]', ...args);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function verbose(...args) {
|
|
533
|
+
if (verboseMode) {
|
|
534
|
+
console.log('[idealyst-plugin]', ...args);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Log plugin initialization
|
|
539
|
+
console.log('[idealyst-plugin] Plugin loaded (AST-based theme analysis)');
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
name: 'idealyst-styles',
|
|
543
|
+
|
|
544
|
+
visitor: {
|
|
545
|
+
Program: {
|
|
546
|
+
enter(path, state) {
|
|
547
|
+
const filename = state.filename || '';
|
|
548
|
+
const opts = state.opts || {};
|
|
549
|
+
|
|
550
|
+
// Check if this file should be processed
|
|
551
|
+
const shouldProcess =
|
|
552
|
+
opts.processAll ||
|
|
553
|
+
(opts.autoProcessPaths?.some(p => filename.includes(p)));
|
|
554
|
+
|
|
555
|
+
state.needsStyleSheetImport = false;
|
|
556
|
+
state.hasStyleSheetImport = false;
|
|
557
|
+
|
|
558
|
+
path.traverse({
|
|
559
|
+
ImportDeclaration(importPath) {
|
|
560
|
+
if (importPath.node.source.value === 'react-native-unistyles') {
|
|
561
|
+
for (const spec of importPath.node.specifiers) {
|
|
562
|
+
if (t.isImportSpecifier(spec) &&
|
|
563
|
+
t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
|
|
564
|
+
state.hasStyleSheetImport = true;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
state.unistylesImportPath = importPath;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
},
|
|
572
|
+
exit(path, state) {
|
|
573
|
+
// Always ensure StyleSheet import and marker when needed
|
|
574
|
+
// (existing import might have been removed by tree-shaking)
|
|
575
|
+
if (state.needsStyleSheetImport) {
|
|
576
|
+
// Check if StyleSheet is currently in the AST
|
|
577
|
+
let hasStyleSheet = false;
|
|
578
|
+
let hasVoidMarker = false;
|
|
579
|
+
let unistylesImport = null;
|
|
580
|
+
|
|
581
|
+
path.traverse({
|
|
582
|
+
ImportDeclaration(importPath) {
|
|
583
|
+
if (importPath.node.source.value === 'react-native-unistyles') {
|
|
584
|
+
unistylesImport = importPath;
|
|
585
|
+
for (const spec of importPath.node.specifiers) {
|
|
586
|
+
if (t.isImportSpecifier(spec) &&
|
|
587
|
+
t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
|
|
588
|
+
hasStyleSheet = true;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
// Check for existing void StyleSheet; marker
|
|
594
|
+
ExpressionStatement(exprPath) {
|
|
595
|
+
if (t.isUnaryExpression(exprPath.node.expression, { operator: 'void' }) &&
|
|
596
|
+
t.isIdentifier(exprPath.node.expression.argument, { name: 'StyleSheet' })) {
|
|
597
|
+
hasVoidMarker = true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
if (!hasStyleSheet) {
|
|
603
|
+
if (unistylesImport) {
|
|
604
|
+
// Add StyleSheet to existing import
|
|
605
|
+
unistylesImport.node.specifiers.push(
|
|
606
|
+
t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))
|
|
607
|
+
);
|
|
608
|
+
} else {
|
|
609
|
+
// Create new import
|
|
610
|
+
const importDecl = t.importDeclaration(
|
|
611
|
+
[t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))],
|
|
612
|
+
t.stringLiteral('react-native-unistyles')
|
|
613
|
+
);
|
|
614
|
+
path.unshiftContainer('body', importDecl);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Add void StyleSheet; marker so Unistyles detects the file
|
|
619
|
+
// This must be present for Unistyles to process the file
|
|
620
|
+
if (!hasVoidMarker) {
|
|
621
|
+
const voidMarker = t.expressionStatement(
|
|
622
|
+
t.unaryExpression('void', t.identifier('StyleSheet'))
|
|
623
|
+
);
|
|
624
|
+
// Insert after imports (find first non-import statement)
|
|
625
|
+
const body = path.node.body;
|
|
626
|
+
let insertIndex = 0;
|
|
627
|
+
for (let i = 0; i < body.length; i++) {
|
|
628
|
+
if (!t.isImportDeclaration(body[i])) {
|
|
629
|
+
insertIndex = i;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
insertIndex = i + 1;
|
|
633
|
+
}
|
|
634
|
+
body.splice(insertIndex, 0, voidMarker);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
CallExpression(path, state) {
|
|
641
|
+
const { node } = path;
|
|
642
|
+
const opts = state.opts || {};
|
|
643
|
+
debugMode = opts.debug === true;
|
|
644
|
+
verboseMode = opts.verbose === true;
|
|
645
|
+
const filename = state.filename || '';
|
|
646
|
+
|
|
647
|
+
const shouldProcess =
|
|
648
|
+
opts.processAll ||
|
|
649
|
+
(opts.autoProcessPaths?.some(p => filename.includes(p)));
|
|
650
|
+
|
|
651
|
+
if (!shouldProcess) return;
|
|
652
|
+
|
|
653
|
+
// ============================================================
|
|
654
|
+
// Handle extendStyle - Store extension AST for later merging
|
|
655
|
+
// ============================================================
|
|
656
|
+
if (t.isIdentifier(node.callee, { name: 'extendStyle' })) {
|
|
657
|
+
debug(`FOUND extendStyle in: ${filename}`);
|
|
658
|
+
|
|
659
|
+
const [componentNameArg, stylesCallback] = node.arguments;
|
|
660
|
+
|
|
661
|
+
if (!t.isStringLiteral(componentNameArg)) {
|
|
662
|
+
debug(` SKIP - componentName is not a string literal`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (!t.isArrowFunctionExpression(stylesCallback) &&
|
|
667
|
+
!t.isFunctionExpression(stylesCallback)) {
|
|
668
|
+
debug(` SKIP - callback is not a function`);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const componentName = componentNameArg.value;
|
|
673
|
+
debug(` Processing extendStyle('${componentName}')`);
|
|
674
|
+
|
|
675
|
+
// Get theme parameter name
|
|
676
|
+
let themeParam = 'theme';
|
|
677
|
+
if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
|
|
678
|
+
themeParam = stylesCallback.params[0].name;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Load theme keys and expand iterators
|
|
682
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd();
|
|
683
|
+
const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
|
|
684
|
+
const expandedVariants = [];
|
|
685
|
+
const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
|
|
686
|
+
|
|
687
|
+
// Store in registry for later merging
|
|
688
|
+
const entry = getOrCreateEntry(componentName);
|
|
689
|
+
entry.extensions.push(expandedCallback);
|
|
690
|
+
entry.themeParam = themeParam;
|
|
691
|
+
|
|
692
|
+
debug(` -> Stored extension for '${componentName}' (${entry.extensions.length} total)`);
|
|
693
|
+
|
|
694
|
+
// Remove the extendStyle call (it's been captured)
|
|
695
|
+
path.remove();
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// ============================================================
|
|
700
|
+
// Handle overrideStyle - Store as override (replaces base)
|
|
701
|
+
// ============================================================
|
|
702
|
+
if (t.isIdentifier(node.callee, { name: 'overrideStyle' })) {
|
|
703
|
+
debug(`FOUND overrideStyle in: ${filename}`);
|
|
704
|
+
|
|
705
|
+
const [componentNameArg, stylesCallback] = node.arguments;
|
|
706
|
+
|
|
707
|
+
if (!t.isStringLiteral(componentNameArg)) {
|
|
708
|
+
debug(` SKIP - componentName is not a string literal`);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (!t.isArrowFunctionExpression(stylesCallback) &&
|
|
713
|
+
!t.isFunctionExpression(stylesCallback)) {
|
|
714
|
+
debug(` SKIP - callback is not a function`);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const componentName = componentNameArg.value;
|
|
719
|
+
debug(` Processing overrideStyle('${componentName}')`);
|
|
720
|
+
|
|
721
|
+
let themeParam = 'theme';
|
|
722
|
+
if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
|
|
723
|
+
themeParam = stylesCallback.params[0].name;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd();
|
|
727
|
+
const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
|
|
728
|
+
const expandedVariants = [];
|
|
729
|
+
const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
|
|
730
|
+
|
|
731
|
+
// Store as override (replaces base entirely)
|
|
732
|
+
const entry = getOrCreateEntry(componentName);
|
|
733
|
+
entry.override = expandedCallback;
|
|
734
|
+
entry.themeParam = themeParam;
|
|
735
|
+
|
|
736
|
+
debug(` -> Stored override for '${componentName}'`);
|
|
737
|
+
|
|
738
|
+
// Remove the overrideStyle call
|
|
739
|
+
path.remove();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// ============================================================
|
|
744
|
+
// Handle defineStyle - Merge with extensions and output StyleSheet.create
|
|
745
|
+
// ============================================================
|
|
746
|
+
if (t.isIdentifier(node.callee, { name: 'defineStyle' })) {
|
|
747
|
+
debug(`FOUND defineStyle in: ${filename}`);
|
|
748
|
+
|
|
749
|
+
const [componentNameArg, stylesCallback] = node.arguments;
|
|
750
|
+
|
|
751
|
+
if (!t.isStringLiteral(componentNameArg)) {
|
|
752
|
+
debug(` SKIP - componentName is not a string literal`);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (!t.isArrowFunctionExpression(stylesCallback) &&
|
|
757
|
+
!t.isFunctionExpression(stylesCallback)) {
|
|
758
|
+
debug(` SKIP - callback is not a function`);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const componentName = componentNameArg.value;
|
|
763
|
+
debug(` Processing defineStyle('${componentName}')`);
|
|
764
|
+
|
|
765
|
+
let themeParam = 'theme';
|
|
766
|
+
if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
|
|
767
|
+
themeParam = stylesCallback.params[0].name;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd();
|
|
771
|
+
const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
|
|
772
|
+
const expandedVariants = [];
|
|
773
|
+
|
|
774
|
+
// Debug for View only
|
|
775
|
+
if (componentName === 'View') {
|
|
776
|
+
console.log(`\n========== VIEW KEYS DEBUG ==========`);
|
|
777
|
+
console.log(`rootDir: ${rootDir}`);
|
|
778
|
+
console.log(`keys.sizes.view: ${JSON.stringify(keys?.sizes?.view)}`);
|
|
779
|
+
console.log(`keys.intents: ${JSON.stringify(keys?.intents)}`);
|
|
780
|
+
console.log(`======================================\n`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
try {
|
|
784
|
+
// Expand iterators in the base callback
|
|
785
|
+
let expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
|
|
786
|
+
|
|
787
|
+
// Check for registered override or extensions
|
|
788
|
+
const entry = getOrCreateEntry(componentName);
|
|
789
|
+
|
|
790
|
+
if (entry.override) {
|
|
791
|
+
// Override completely replaces base
|
|
792
|
+
debug(` -> Using override for '${componentName}'`);
|
|
793
|
+
expandedCallback = entry.override;
|
|
794
|
+
} else if (entry.extensions.length > 0) {
|
|
795
|
+
// Merge extensions into base
|
|
796
|
+
debug(` -> Merging ${entry.extensions.length} extensions for '${componentName}'`);
|
|
797
|
+
for (const ext of entry.extensions) {
|
|
798
|
+
expandedCallback = mergeCallbackBodies(t, expandedCallback, ext);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (!expandedCallback) {
|
|
803
|
+
console.error(`[idealyst-plugin] ERROR: expandedCallback is null/undefined for ${componentName}`);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Transform to StyleSheet.create
|
|
808
|
+
state.needsStyleSheetImport = true;
|
|
809
|
+
|
|
810
|
+
const newCall = t.callExpression(
|
|
811
|
+
t.memberExpression(
|
|
812
|
+
t.identifier('StyleSheet'),
|
|
813
|
+
t.identifier('create')
|
|
814
|
+
),
|
|
815
|
+
[expandedCallback]
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
path.replaceWith(newCall);
|
|
819
|
+
|
|
820
|
+
debug(` -> Transformed to StyleSheet.create`);
|
|
821
|
+
if (expandedVariants.length > 0) {
|
|
822
|
+
debug(` Expanded: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Output generated code when verbose
|
|
826
|
+
if (verboseMode) {
|
|
827
|
+
const generate = require('@babel/generator').default;
|
|
828
|
+
const output = generate(newCall);
|
|
829
|
+
console.log(`[idealyst-plugin] Generated code for ${componentName}:`);
|
|
830
|
+
console.log(output.code.substring(0, 2000) + (output.code.length > 2000 ? '...' : ''));
|
|
831
|
+
}
|
|
832
|
+
} catch (err) {
|
|
833
|
+
console.error(`[idealyst-plugin] ERROR transforming defineStyle('${componentName}'):`, err);
|
|
834
|
+
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// ============================================================
|
|
839
|
+
// Handle StyleSheet.create - Just expand $iterator patterns
|
|
840
|
+
// ============================================================
|
|
841
|
+
if (t.isMemberExpression(node.callee) &&
|
|
842
|
+
t.isIdentifier(node.callee.object, { name: 'StyleSheet' }) &&
|
|
843
|
+
t.isIdentifier(node.callee.property, { name: 'create' })) {
|
|
844
|
+
|
|
845
|
+
const [stylesCallback] = node.arguments;
|
|
846
|
+
|
|
847
|
+
if (!t.isArrowFunctionExpression(stylesCallback) &&
|
|
848
|
+
!t.isFunctionExpression(stylesCallback)) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
let themeParam = 'theme';
|
|
853
|
+
if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
|
|
854
|
+
themeParam = stylesCallback.params[0].name;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd();
|
|
858
|
+
const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
|
|
859
|
+
const expandedVariants = [];
|
|
860
|
+
|
|
861
|
+
const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
|
|
862
|
+
node.arguments[0] = expandedCallback;
|
|
863
|
+
|
|
864
|
+
if (expandedVariants.length > 0) {
|
|
865
|
+
debug(` -> Expanded $iterator patterns in StyleSheet.create: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
};
|