@homebound/truss 2.0.13 → 2.1.0-next.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.
@@ -1,6 +1,546 @@
1
1
  // src/plugin/index.ts
2
- import { readFileSync, existsSync } from "fs";
3
- import { resolve, dirname, isAbsolute } from "path";
2
+ import { readFileSync, writeFileSync, existsSync } from "fs";
3
+ import { resolve, dirname, isAbsolute, join } from "path";
4
+
5
+ // src/plugin/emit-truss.ts
6
+ import * as t from "@babel/types";
7
+ var PSEUDO_SUFFIX = {
8
+ ":hover": "_h",
9
+ ":focus": "_f",
10
+ ":focus-visible": "_fv",
11
+ ":active": "_a",
12
+ ":disabled": "_d"
13
+ };
14
+ var PSEUDO_ORDER = [":hover", ":focus", ":focus-visible", ":active", ":disabled"];
15
+ var RELATIONSHIP_SHORT = {
16
+ ancestor: "anc",
17
+ descendant: "desc",
18
+ siblingAfter: "sibA",
19
+ siblingBefore: "sibB",
20
+ anySibling: "anyS"
21
+ };
22
+ var DEFAULT_MARKER_CLASS = "__truss_m";
23
+ function markerClassName(markerNode) {
24
+ if (!markerNode) return DEFAULT_MARKER_CLASS;
25
+ if (markerNode.type === "Identifier" && markerNode.name) {
26
+ return `__truss_m_${markerNode.name}`;
27
+ }
28
+ return `${DEFAULT_MARKER_CLASS}_marker`;
29
+ }
30
+ function whenPrefix(whenPseudo) {
31
+ const rel = RELATIONSHIP_SHORT[whenPseudo.relationship ?? "ancestor"] ?? "anc";
32
+ const pseudoTag = PSEUDO_SUFFIX[whenPseudo.pseudo]?.replace(/^_/, "") ?? whenPseudo.pseudo.replace(/^:/, "");
33
+ const markerPart = whenPseudo.markerNode?.type === "Identifier" ? `${whenPseudo.markerNode.name}_` : "";
34
+ return `wh_${rel}_${pseudoTag}_${markerPart}`;
35
+ }
36
+ function conditionPrefix(pseudoClass, mediaQuery, pseudoElement, breakpoints) {
37
+ const parts = [];
38
+ if (pseudoElement) {
39
+ parts.push(`${pseudoElement.replace(/^::/, "")}_`);
40
+ }
41
+ if (mediaQuery && breakpoints) {
42
+ const bpKey = Object.entries(breakpoints).find(([, v]) => v === mediaQuery)?.[0];
43
+ if (bpKey) {
44
+ const shortName = bpKey.replace(/^if/, "").toLowerCase();
45
+ parts.push(`${shortName}_`);
46
+ } else {
47
+ parts.push("mq_");
48
+ }
49
+ } else if (mediaQuery) {
50
+ parts.push("mq_");
51
+ }
52
+ if (pseudoClass) {
53
+ const tag = PSEUDO_SUFFIX[pseudoClass];
54
+ if (tag) parts.push(`${tag.replace(/^_/, "")}_`);
55
+ else parts.push(`${pseudoClass.replace(/^:/, "")}_`);
56
+ }
57
+ return parts.join("");
58
+ }
59
+ function camelToKebab(s) {
60
+ return s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^(webkit|moz|ms)-/, "-$1-");
61
+ }
62
+ function cleanValueForClassName(value) {
63
+ let cleaned = value;
64
+ if (cleaned.startsWith("-")) {
65
+ cleaned = "neg" + cleaned.slice(1);
66
+ }
67
+ return cleaned.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
68
+ }
69
+ function buildLonghandLookup(mapping) {
70
+ const lookup = /* @__PURE__ */ new Map();
71
+ for (const [abbrev, entry] of Object.entries(mapping.abbreviations)) {
72
+ if (entry.kind !== "static") continue;
73
+ const props = Object.keys(entry.defs);
74
+ if (props.length !== 1) continue;
75
+ const prop = props[0];
76
+ const value = String(entry.defs[prop]);
77
+ const key = `${prop}\0${value}`;
78
+ if (!lookup.has(key)) {
79
+ lookup.set(key, abbrev);
80
+ }
81
+ }
82
+ return lookup;
83
+ }
84
+ var cachedMapping = null;
85
+ var cachedLookup = null;
86
+ function getLonghandLookup(mapping) {
87
+ if (cachedMapping !== mapping) {
88
+ cachedMapping = mapping;
89
+ cachedLookup = buildLonghandLookup(mapping);
90
+ }
91
+ return cachedLookup;
92
+ }
93
+ function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
94
+ const abbrev = seg.key.split("__")[0];
95
+ if (seg.argResolved !== void 0) {
96
+ const valuePart = cleanValueForClassName(seg.argResolved);
97
+ if (isMultiProp) {
98
+ const lookup = getLonghandLookup(mapping);
99
+ const canonical = lookup.get(`${cssProp}\0${cssValue}`);
100
+ if (canonical) return canonical;
101
+ return `${abbrev}_${valuePart}_${cssProp}`;
102
+ }
103
+ return `${abbrev}_${valuePart}`;
104
+ }
105
+ if (isMultiProp) {
106
+ const lookup = getLonghandLookup(mapping);
107
+ const canonical = lookup.get(`${cssProp}\0${cssValue}`);
108
+ if (canonical) return canonical;
109
+ return `${abbrev}_${cssProp}`;
110
+ }
111
+ return abbrev;
112
+ }
113
+ function collectAtomicRules(chains, mapping) {
114
+ const rules = /* @__PURE__ */ new Map();
115
+ let needsMaybeInc = false;
116
+ for (const chain of chains) {
117
+ for (const part of chain.parts) {
118
+ const segs = part.type === "unconditional" ? part.segments : [...part.thenSegments, ...part.elseSegments];
119
+ for (const seg of segs) {
120
+ if (seg.error || seg.styleArrayArg || seg.typographyLookup) continue;
121
+ if (seg.whenPseudo) {
122
+ if (seg.variableProps) {
123
+ if (seg.incremented) needsMaybeInc = true;
124
+ collectWhenVariableRules(rules, seg, mapping);
125
+ } else {
126
+ collectWhenStaticRules(rules, seg, mapping);
127
+ }
128
+ continue;
129
+ }
130
+ if (seg.variableProps) {
131
+ if (seg.incremented) needsMaybeInc = true;
132
+ collectVariableRules(rules, seg, mapping);
133
+ } else {
134
+ collectStaticRules(rules, seg, mapping);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return { rules, needsMaybeInc };
140
+ }
141
+ function collectStaticRules(rules, seg, mapping) {
142
+ const rawDefs = unwrapDefs(seg.defs, seg.pseudoElement);
143
+ const prefix = conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
144
+ const isMultiProp = Object.keys(rawDefs).length > 1;
145
+ for (const [cssProp, value] of Object.entries(rawDefs)) {
146
+ const cssValue = extractLeafValue(value);
147
+ if (cssValue === null) continue;
148
+ const baseName = computeStaticBaseName(seg, cssProp, String(cssValue), isMultiProp, mapping);
149
+ const className = prefix ? `${prefix}${baseName}` : baseName;
150
+ if (!rules.has(className)) {
151
+ rules.set(className, {
152
+ className,
153
+ cssProperty: camelToKebab(cssProp),
154
+ cssValue: String(cssValue),
155
+ pseudoClass: seg.pseudoClass ?? void 0,
156
+ mediaQuery: seg.mediaQuery ?? void 0,
157
+ pseudoElement: seg.pseudoElement ?? void 0
158
+ });
159
+ }
160
+ }
161
+ }
162
+ function collectVariableRules(rules, seg, mapping) {
163
+ const prefix = conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
164
+ const baseKey = seg.key.split("__")[0];
165
+ for (const prop of seg.variableProps) {
166
+ const className = prefix ? `${prefix}${baseKey}_var` : `${baseKey}_var`;
167
+ const varName = toCssVariableName(className, baseKey, prop);
168
+ if (!rules.has(className)) {
169
+ rules.set(className, {
170
+ className,
171
+ cssProperty: camelToKebab(prop),
172
+ cssValue: `var(${varName})`,
173
+ pseudoClass: seg.pseudoClass ?? void 0,
174
+ mediaQuery: seg.mediaQuery ?? void 0,
175
+ pseudoElement: seg.pseudoElement ?? void 0,
176
+ cssVarName: varName
177
+ });
178
+ }
179
+ }
180
+ if (seg.variableExtraDefs) {
181
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
182
+ const extraBase = `${baseKey}_${cssProp}`;
183
+ const extraName = prefix ? `${prefix}${extraBase}` : extraBase;
184
+ if (!rules.has(extraName)) {
185
+ rules.set(extraName, {
186
+ className: extraName,
187
+ cssProperty: camelToKebab(cssProp),
188
+ cssValue: String(value),
189
+ pseudoClass: seg.pseudoClass ?? void 0,
190
+ mediaQuery: seg.mediaQuery ?? void 0,
191
+ pseudoElement: seg.pseudoElement ?? void 0
192
+ });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ function collectWhenStaticRules(rules, seg, mapping) {
198
+ const wp = seg.whenPseudo;
199
+ const prefix = whenPrefix(wp);
200
+ const rawDefs = seg.defs;
201
+ const isMultiProp = Object.keys(rawDefs).length > 1;
202
+ const mClass = markerClassName(wp.markerNode);
203
+ for (const [cssProp, value] of Object.entries(rawDefs)) {
204
+ const cssValue = typeof value === "string" || typeof value === "number" ? value : extractLeafValue(value);
205
+ if (cssValue === null) continue;
206
+ const baseName = computeStaticBaseName(seg, cssProp, String(cssValue), isMultiProp, mapping);
207
+ const className = `${prefix}${baseName}`;
208
+ if (!rules.has(className)) {
209
+ rules.set(className, {
210
+ className,
211
+ cssProperty: camelToKebab(cssProp),
212
+ cssValue: String(cssValue),
213
+ whenSelector: {
214
+ relationship: wp.relationship ?? "ancestor",
215
+ markerClass: mClass,
216
+ pseudo: wp.pseudo
217
+ }
218
+ });
219
+ }
220
+ }
221
+ }
222
+ function collectWhenVariableRules(rules, seg, mapping) {
223
+ const wp = seg.whenPseudo;
224
+ const prefix = whenPrefix(wp);
225
+ const baseKey = seg.key.split("__")[0];
226
+ const mClass = markerClassName(wp.markerNode);
227
+ for (const prop of seg.variableProps) {
228
+ const className = `${prefix}${baseKey}_var`;
229
+ const varName = toCssVariableName(className, baseKey, prop);
230
+ if (!rules.has(className)) {
231
+ rules.set(className, {
232
+ className,
233
+ cssProperty: camelToKebab(prop),
234
+ cssValue: `var(${varName})`,
235
+ cssVarName: varName,
236
+ whenSelector: {
237
+ relationship: wp.relationship ?? "ancestor",
238
+ markerClass: mClass,
239
+ pseudo: wp.pseudo
240
+ }
241
+ });
242
+ }
243
+ }
244
+ if (seg.variableExtraDefs) {
245
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
246
+ const extraName = `${prefix}${baseKey}_${cssProp}`;
247
+ if (!rules.has(extraName)) {
248
+ rules.set(extraName, {
249
+ className: extraName,
250
+ cssProperty: camelToKebab(cssProp),
251
+ cssValue: String(value),
252
+ whenSelector: {
253
+ relationship: wp.relationship ?? "ancestor",
254
+ markerClass: mClass,
255
+ pseudo: wp.pseudo
256
+ }
257
+ });
258
+ }
259
+ }
260
+ }
261
+ }
262
+ function unwrapDefs(defs, pseudoElement) {
263
+ let result = defs;
264
+ if (pseudoElement && result[pseudoElement] && typeof result[pseudoElement] === "object") {
265
+ result = result[pseudoElement];
266
+ }
267
+ const unwrapped = {};
268
+ for (const [prop, val] of Object.entries(result)) {
269
+ unwrapped[prop] = extractLeafValue(val) ?? val;
270
+ }
271
+ return unwrapped;
272
+ }
273
+ function extractLeafValue(value) {
274
+ if (typeof value === "string" || typeof value === "number") return value;
275
+ if (value === null) return null;
276
+ if (typeof value === "object") {
277
+ const obj = value;
278
+ for (const [k, v] of Object.entries(obj)) {
279
+ if (k === "default") continue;
280
+ if (typeof v === "string" || typeof v === "number") return v;
281
+ if (typeof v === "object" && v !== null) return extractLeafValue(v);
282
+ }
283
+ if ("default" in obj && obj.default !== null) {
284
+ return extractLeafValue(obj.default);
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+ function generateCssText(rules) {
290
+ const allRules = Array.from(rules.values());
291
+ const base = [];
292
+ const pseudo = /* @__PURE__ */ new Map();
293
+ const pseudoElement = [];
294
+ const whenRules = [];
295
+ const media = [];
296
+ const mediaPseudo = [];
297
+ const mediaPseudoElement = [];
298
+ for (const rule of allRules) {
299
+ if (rule.whenSelector) {
300
+ whenRules.push(rule);
301
+ } else if (rule.mediaQuery && rule.pseudoClass) {
302
+ mediaPseudo.push(rule);
303
+ } else if (rule.mediaQuery && rule.pseudoElement) {
304
+ mediaPseudoElement.push(rule);
305
+ } else if (rule.mediaQuery) {
306
+ media.push(rule);
307
+ } else if (rule.pseudoClass && rule.pseudoElement) {
308
+ const tier = pseudo.get(rule.pseudoClass) ?? [];
309
+ tier.push(rule);
310
+ pseudo.set(rule.pseudoClass, tier);
311
+ } else if (rule.pseudoElement) {
312
+ pseudoElement.push(rule);
313
+ } else if (rule.pseudoClass) {
314
+ const tier = pseudo.get(rule.pseudoClass) ?? [];
315
+ tier.push(rule);
316
+ pseudo.set(rule.pseudoClass, tier);
317
+ } else {
318
+ base.push(rule);
319
+ }
320
+ }
321
+ const lines = [];
322
+ for (const rule of base) {
323
+ lines.push(formatBaseRule(rule));
324
+ }
325
+ for (const pc of PSEUDO_ORDER) {
326
+ const tier = pseudo.get(pc);
327
+ if (!tier) continue;
328
+ for (const rule of tier) {
329
+ lines.push(formatPseudoRule(rule));
330
+ }
331
+ }
332
+ for (const [pc, tier] of Array.from(pseudo.entries())) {
333
+ if (PSEUDO_ORDER.includes(pc)) continue;
334
+ for (const rule of tier) {
335
+ lines.push(formatPseudoRule(rule));
336
+ }
337
+ }
338
+ for (const rule of pseudoElement) {
339
+ lines.push(formatPseudoElementRule(rule));
340
+ }
341
+ for (const rule of whenRules) {
342
+ lines.push(formatWhenRule(rule));
343
+ }
344
+ for (const rule of media) {
345
+ lines.push(formatMediaRule(rule));
346
+ }
347
+ for (const rule of mediaPseudo) {
348
+ lines.push(formatMediaPseudoRule(rule));
349
+ }
350
+ for (const rule of mediaPseudoElement) {
351
+ lines.push(formatMediaPseudoElementRule(rule));
352
+ }
353
+ for (const rule of allRules) {
354
+ if (rule.cssVarName) {
355
+ lines.push(`@property ${rule.cssVarName} {
356
+ syntax: "*";
357
+ inherits: false;
358
+ }`);
359
+ }
360
+ }
361
+ return lines.join("\n");
362
+ }
363
+ function formatBaseRule(rule) {
364
+ return `.${rule.className} {
365
+ ${rule.cssProperty}: ${rule.cssValue};
366
+ }`;
367
+ }
368
+ function formatPseudoRule(rule) {
369
+ const pe = rule.pseudoElement ? rule.pseudoElement : "";
370
+ return `.${rule.className}${rule.pseudoClass}${pe} {
371
+ ${rule.cssProperty}: ${rule.cssValue};
372
+ }`;
373
+ }
374
+ function formatPseudoElementRule(rule) {
375
+ return `.${rule.className}${rule.pseudoElement} {
376
+ ${rule.cssProperty}: ${rule.cssValue};
377
+ }`;
378
+ }
379
+ function formatWhenRule(rule) {
380
+ const whenSelector = rule.whenSelector;
381
+ if (!whenSelector) {
382
+ return formatBaseRule(rule);
383
+ }
384
+ const markerSelector = `.${whenSelector.markerClass}${whenSelector.pseudo}`;
385
+ const targetSelector = `.${rule.className}`;
386
+ if (whenSelector.relationship === "ancestor") {
387
+ return `${markerSelector} ${targetSelector} {
388
+ ${rule.cssProperty}: ${rule.cssValue};
389
+ }`;
390
+ }
391
+ if (whenSelector.relationship === "descendant") {
392
+ return `${targetSelector}:has(${markerSelector}) {
393
+ ${rule.cssProperty}: ${rule.cssValue};
394
+ }`;
395
+ }
396
+ if (whenSelector.relationship === "siblingAfter") {
397
+ return `${targetSelector}:has(~ ${markerSelector}) {
398
+ ${rule.cssProperty}: ${rule.cssValue};
399
+ }`;
400
+ }
401
+ if (whenSelector.relationship === "siblingBefore") {
402
+ return `${markerSelector} ~ ${targetSelector} {
403
+ ${rule.cssProperty}: ${rule.cssValue};
404
+ }`;
405
+ }
406
+ if (whenSelector.relationship === "anySibling") {
407
+ return `${targetSelector}:has(~ ${markerSelector}), ${markerSelector} ~ ${targetSelector} {
408
+ ${rule.cssProperty}: ${rule.cssValue};
409
+ }`;
410
+ }
411
+ return `${markerSelector} ${targetSelector} {
412
+ ${rule.cssProperty}: ${rule.cssValue};
413
+ }`;
414
+ }
415
+ function formatMediaRule(rule) {
416
+ return `${rule.mediaQuery} {
417
+ .${rule.className}.${rule.className} {
418
+ ${rule.cssProperty}: ${rule.cssValue};
419
+ }
420
+ }`;
421
+ }
422
+ function formatMediaPseudoRule(rule) {
423
+ return `${rule.mediaQuery} {
424
+ .${rule.className}.${rule.className}${rule.pseudoClass} {
425
+ ${rule.cssProperty}: ${rule.cssValue};
426
+ }
427
+ }`;
428
+ }
429
+ function formatMediaPseudoElementRule(rule) {
430
+ const pe = rule.pseudoElement ?? "";
431
+ return `${rule.mediaQuery} {
432
+ .${rule.className}.${rule.className}${pe} {
433
+ ${rule.cssProperty}: ${rule.cssValue};
434
+ }
435
+ }`;
436
+ }
437
+ function buildStyleHashProperties(segments, mapping, maybeIncHelperName) {
438
+ const propGroups = /* @__PURE__ */ new Map();
439
+ for (const seg of segments) {
440
+ if (seg.error || seg.styleArrayArg || seg.typographyLookup) continue;
441
+ if (seg.variableProps) {
442
+ const prefix = seg.whenPseudo ? whenPrefix(seg.whenPseudo) : conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
443
+ const baseKey = seg.key.split("__")[0];
444
+ for (const prop of seg.variableProps) {
445
+ const className = prefix ? `${prefix}${baseKey}_var` : `${baseKey}_var`;
446
+ const varName = toCssVariableName(className, baseKey, prop);
447
+ if (!propGroups.has(prop)) propGroups.set(prop, []);
448
+ propGroups.get(prop).push({
449
+ className,
450
+ isVariable: true,
451
+ varName,
452
+ argNode: seg.argNode,
453
+ incremented: seg.incremented,
454
+ appendPx: seg.appendPx
455
+ });
456
+ }
457
+ if (seg.variableExtraDefs) {
458
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
459
+ const extraBase = `${baseKey}_${cssProp}`;
460
+ const extraName = prefix ? `${prefix}${extraBase}` : extraBase;
461
+ if (!propGroups.has(cssProp)) propGroups.set(cssProp, []);
462
+ propGroups.get(cssProp).push({ className: extraName, isVariable: false });
463
+ }
464
+ }
465
+ } else {
466
+ const rawDefs = seg.whenPseudo ? seg.defs : unwrapDefs(seg.defs, seg.pseudoElement);
467
+ const prefix = seg.whenPseudo ? whenPrefix(seg.whenPseudo) : conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
468
+ const isMultiProp = Object.keys(rawDefs).length > 1;
469
+ for (const cssProp of Object.keys(rawDefs)) {
470
+ const val = extractLeafValue(rawDefs[cssProp]);
471
+ if (val === null) continue;
472
+ const baseName = computeStaticBaseName(seg, cssProp, String(val), isMultiProp, mapping);
473
+ const className = prefix ? `${prefix}${baseName}` : baseName;
474
+ if (!propGroups.has(cssProp)) propGroups.set(cssProp, []);
475
+ propGroups.get(cssProp).push({ className, isVariable: false });
476
+ }
477
+ }
478
+ }
479
+ const properties = [];
480
+ for (const [cssProp, entries] of Array.from(propGroups.entries())) {
481
+ const classNames = entries.map((e) => e.className).join(" ");
482
+ const variableEntries = entries.filter((e) => e.isVariable);
483
+ if (variableEntries.length > 0) {
484
+ const varsProps = [];
485
+ for (const dyn of variableEntries) {
486
+ let valueExpr = dyn.argNode;
487
+ if (dyn.incremented) {
488
+ valueExpr = t.callExpression(t.identifier(maybeIncHelperName ?? "__maybeInc"), [valueExpr]);
489
+ } else if (dyn.appendPx) {
490
+ valueExpr = t.templateLiteral(
491
+ [t.templateElement({ raw: "", cooked: "" }, false), t.templateElement({ raw: "px", cooked: "px" }, true)],
492
+ [valueExpr]
493
+ );
494
+ }
495
+ varsProps.push(t.objectProperty(t.stringLiteral(dyn.varName), valueExpr));
496
+ }
497
+ const tuple = t.arrayExpression([t.stringLiteral(classNames), t.objectExpression(varsProps)]);
498
+ properties.push(t.objectProperty(toPropertyKey(cssProp), tuple));
499
+ } else {
500
+ properties.push(t.objectProperty(toPropertyKey(cssProp), t.stringLiteral(classNames)));
501
+ }
502
+ }
503
+ return properties;
504
+ }
505
+ function toCssVariableName(className, baseKey, cssProp) {
506
+ const baseClassName = `${baseKey}_var`;
507
+ const conditionPrefix2 = className.endsWith(baseClassName) ? className.slice(0, -baseClassName.length) : "";
508
+ return `--${conditionPrefix2}${cssProp}`;
509
+ }
510
+ function buildMaybeIncDeclaration(helperName, increment) {
511
+ const incParam = t.identifier("inc");
512
+ const body = t.blockStatement([
513
+ t.returnStatement(
514
+ t.conditionalExpression(
515
+ t.binaryExpression("===", t.unaryExpression("typeof", incParam), t.stringLiteral("string")),
516
+ incParam,
517
+ t.templateLiteral(
518
+ [t.templateElement({ raw: "", cooked: "" }, false), t.templateElement({ raw: "px", cooked: "px" }, true)],
519
+ [t.binaryExpression("*", incParam, t.numericLiteral(increment))]
520
+ )
521
+ )
522
+ )
523
+ ]);
524
+ return t.variableDeclaration("const", [
525
+ t.variableDeclarator(t.identifier(helperName), t.arrowFunctionExpression([incParam], body))
526
+ ]);
527
+ }
528
+ function toPropertyKey(key) {
529
+ return isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(key);
530
+ }
531
+ function isValidIdentifier(s) {
532
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
533
+ }
534
+ function buildRuntimeLookupDeclaration(lookupName, segmentsByName, mapping) {
535
+ const properties = [];
536
+ for (const [name, segs] of Object.entries(segmentsByName)) {
537
+ const hashProps = buildStyleHashProperties(segs, mapping);
538
+ properties.push(t.objectProperty(t.identifier(name), t.objectExpression(hashProps)));
539
+ }
540
+ return t.variableDeclaration("const", [
541
+ t.variableDeclarator(t.identifier(lookupName), t.objectExpression(properties))
542
+ ]);
543
+ }
4
544
 
5
545
  // src/plugin/transform.ts
6
546
  import { parse } from "@babel/parser";
@@ -10,7 +550,7 @@ import * as t4 from "@babel/types";
10
550
  import { basename } from "path";
11
551
 
12
552
  // src/plugin/resolve-chain.ts
13
- function resolveFullChain(chain, mapping) {
553
+ function resolveFullChain(chain, mapping, options) {
14
554
  const parts = [];
15
555
  const markers = [];
16
556
  const filteredChain = [];
@@ -41,9 +581,10 @@ function resolveFullChain(chain, mapping) {
41
581
  continue;
42
582
  }
43
583
  if (currentNodes.length > 0) {
584
+ const unconditionalSegs = resolveChain(currentNodes, mapping);
44
585
  parts.push({
45
586
  type: "unconditional",
46
- segments: mergeOverlappingConditions(resolveChain(currentNodes, mapping))
587
+ segments: options?.skipMerge ? unconditionalSegs : mergeOverlappingConditions(unconditionalSegs)
47
588
  });
48
589
  currentNodes = [];
49
590
  }
@@ -67,11 +608,13 @@ function resolveFullChain(chain, mapping) {
67
608
  }
68
609
  i++;
69
610
  }
611
+ const thenSegs = resolveChain(thenNodes, mapping);
612
+ const elseSegs = resolveChain(elseNodes, mapping);
70
613
  parts.push({
71
614
  type: "conditional",
72
615
  conditionNode: node.conditionNode,
73
- thenSegments: mergeOverlappingConditions(resolveChain(thenNodes, mapping)),
74
- elseSegments: mergeOverlappingConditions(resolveChain(elseNodes, mapping))
616
+ thenSegments: options?.skipMerge ? thenSegs : mergeOverlappingConditions(thenSegs),
617
+ elseSegments: options?.skipMerge ? elseSegs : mergeOverlappingConditions(elseSegs)
75
618
  });
76
619
  } else {
77
620
  currentNodes.push(node);
@@ -79,7 +622,11 @@ function resolveFullChain(chain, mapping) {
79
622
  }
80
623
  }
81
624
  if (currentNodes.length > 0) {
82
- parts.push({ type: "unconditional", segments: mergeOverlappingConditions(resolveChain(currentNodes, mapping)) });
625
+ const remainingSegs = resolveChain(currentNodes, mapping);
626
+ parts.push({
627
+ type: "unconditional",
628
+ segments: options?.skipMerge ? remainingSegs : mergeOverlappingConditions(remainingSegs)
629
+ });
83
630
  }
84
631
  const segmentErrors = [];
85
632
  for (const part of parts) {
@@ -139,7 +686,14 @@ function resolveChain(chain, mapping) {
139
686
  continue;
140
687
  }
141
688
  if (abbr === "add") {
142
- const seg = resolveAddCall(node, mapping, currentMediaQuery, currentPseudoClass, currentPseudoElement);
689
+ const seg = resolveAddCall(
690
+ node,
691
+ mapping,
692
+ currentMediaQuery,
693
+ currentPseudoClass,
694
+ currentPseudoElement,
695
+ currentWhenPseudo
696
+ );
143
697
  segments.push(seg);
144
698
  continue;
145
699
  }
@@ -184,15 +738,16 @@ function resolveChain(chain, mapping) {
184
738
  if (!entry) {
185
739
  throw new UnsupportedPatternError(`Unknown abbreviation "${abbr}"`);
186
740
  }
187
- if (entry.kind === "dynamic") {
188
- const seg = resolveDynamicCall(
741
+ if (entry.kind === "variable") {
742
+ const seg = resolveVariableCall(
189
743
  abbr,
190
744
  entry,
191
745
  node,
192
746
  mapping,
193
747
  currentMediaQuery,
194
748
  currentPseudoClass,
195
- currentPseudoElement
749
+ currentPseudoElement,
750
+ currentWhenPseudo
196
751
  );
197
752
  segments.push(seg);
198
753
  } else if (entry.kind === "delegate") {
@@ -203,7 +758,8 @@ function resolveChain(chain, mapping) {
203
758
  mapping,
204
759
  currentMediaQuery,
205
760
  currentPseudoClass,
206
- currentPseudoElement
761
+ currentPseudoElement,
762
+ currentWhenPseudo
207
763
  );
208
764
  segments.push(seg);
209
765
  } else {
@@ -267,7 +823,7 @@ function resolveTypographyEntry(name, mapping, mediaQuery, pseudoClass, pseudoEl
267
823
  }
268
824
  const resolved = resolveEntry(name, entry, mapping, mediaQuery, pseudoClass, pseudoElement, null);
269
825
  for (const segment of resolved) {
270
- if (segment.dynamicProps || segment.whenPseudo) {
826
+ if (segment.variableProps || segment.whenPseudo) {
271
827
  throw new UnsupportedPatternError(`Typography abbreviation "${name}" cannot require runtime arguments`);
272
828
  }
273
829
  }
@@ -311,19 +867,43 @@ function resolveEntry(abbr, entry, mapping, mediaQuery, pseudoClass, pseudoEleme
311
867
  }
312
868
  return result;
313
869
  }
314
- case "dynamic":
870
+ case "variable":
315
871
  case "delegate":
316
872
  throw new UnsupportedPatternError(`Abbreviation "${abbr}" requires arguments \u2014 use ${abbr}() not .${abbr}`);
317
873
  default:
318
874
  throw new UnsupportedPatternError(`Unhandled entry kind for "${abbr}"`);
319
875
  }
320
876
  }
321
- function resolveDynamicCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement) {
877
+ function resolveVariableCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
322
878
  if (node.args.length !== 1) {
323
879
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
324
880
  }
325
881
  const argAst = node.args[0];
326
882
  const literalValue = tryEvaluateLiteral(argAst, entry.incremented, mapping.increment);
883
+ if (whenPseudo) {
884
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
885
+ if (literalValue !== null) {
886
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
887
+ const key = `${abbr}__${keySuffix}__${wpSuffix}`;
888
+ const defs = {};
889
+ for (const prop of entry.props) {
890
+ defs[prop] = literalValue;
891
+ }
892
+ if (entry.extraDefs) Object.assign(defs, entry.extraDefs);
893
+ return { key, defs, whenPseudo, argResolved: literalValue };
894
+ } else {
895
+ const key = `${abbr}__${wpSuffix}`;
896
+ return {
897
+ key,
898
+ defs: {},
899
+ whenPseudo,
900
+ variableProps: entry.props,
901
+ incremented: entry.incremented,
902
+ variableExtraDefs: entry.extraDefs,
903
+ argNode: argAst
904
+ };
905
+ }
906
+ }
327
907
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
328
908
  if (literalValue !== null) {
329
909
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
@@ -351,23 +931,48 @@ function resolveDynamicCall(abbr, entry, node, mapping, mediaQuery, pseudoClass,
351
931
  defs: {},
352
932
  mediaQuery,
353
933
  pseudoClass,
354
- dynamicProps: entry.props,
934
+ variableProps: entry.props,
355
935
  incremented: entry.incremented,
356
- dynamicExtraDefs: entry.extraDefs,
936
+ variableExtraDefs: entry.extraDefs,
357
937
  argNode: argAst
358
938
  };
359
939
  }
360
940
  }
361
- function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement) {
941
+ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
362
942
  const targetEntry = mapping.abbreviations[entry.target];
363
- if (!targetEntry || targetEntry.kind !== "dynamic") {
364
- throw new UnsupportedPatternError(`Delegate "${abbr}" targets "${entry.target}" which is not a dynamic entry`);
943
+ if (!targetEntry || targetEntry.kind !== "variable") {
944
+ throw new UnsupportedPatternError(`Delegate "${abbr}" targets "${entry.target}" which is not a variable entry`);
365
945
  }
366
946
  if (node.args.length !== 1) {
367
947
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
368
948
  }
369
949
  const argAst = node.args[0];
370
950
  const literalValue = tryEvaluatePxLiteral(argAst);
951
+ if (whenPseudo) {
952
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
953
+ if (literalValue !== null) {
954
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
955
+ const key = `${entry.target}__${keySuffix}__${wpSuffix}`;
956
+ const defs = {};
957
+ for (const prop of targetEntry.props) {
958
+ defs[prop] = literalValue;
959
+ }
960
+ if (targetEntry.extraDefs) Object.assign(defs, targetEntry.extraDefs);
961
+ return { key, defs, whenPseudo, argResolved: literalValue };
962
+ } else {
963
+ const key = `${entry.target}__${wpSuffix}`;
964
+ return {
965
+ key,
966
+ defs: {},
967
+ whenPseudo,
968
+ variableProps: targetEntry.props,
969
+ incremented: false,
970
+ appendPx: true,
971
+ variableExtraDefs: targetEntry.extraDefs,
972
+ argNode: argAst
973
+ };
974
+ }
975
+ }
371
976
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
372
977
  if (literalValue !== null) {
373
978
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
@@ -396,15 +1001,15 @@ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
396
1001
  mediaQuery,
397
1002
  pseudoClass,
398
1003
  pseudoElement,
399
- dynamicProps: targetEntry.props,
1004
+ variableProps: targetEntry.props,
400
1005
  incremented: false,
401
1006
  appendPx: true,
402
- dynamicExtraDefs: targetEntry.extraDefs,
1007
+ variableExtraDefs: targetEntry.extraDefs,
403
1008
  argNode: argAst
404
1009
  };
405
1010
  }
406
1011
  }
407
- function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
1012
+ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
408
1013
  if (node.args.length === 1) {
409
1014
  const styleArg = node.args[0];
410
1015
  if (styleArg.type === "SpreadElement") {
@@ -433,6 +1038,17 @@ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
433
1038
  const propName = propArg.value;
434
1039
  const valueArg = node.args[1];
435
1040
  const literalValue = tryEvaluateAddLiteral(valueArg);
1041
+ if (whenPseudo) {
1042
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
1043
+ if (literalValue !== null) {
1044
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1045
+ const key = `add_${propName}__${keySuffix}__${wpSuffix}`;
1046
+ return { key, defs: { [propName]: literalValue }, whenPseudo, argResolved: literalValue };
1047
+ } else {
1048
+ const key = `add_${propName}__${wpSuffix}`;
1049
+ return { key, defs: {}, whenPseudo, variableProps: [propName], incremented: false, argNode: valueArg };
1050
+ }
1051
+ }
436
1052
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
437
1053
  if (literalValue !== null) {
438
1054
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
@@ -455,7 +1071,7 @@ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
455
1071
  mediaQuery,
456
1072
  pseudoClass,
457
1073
  pseudoElement,
458
- dynamicProps: [propName],
1074
+ variableProps: [propName],
459
1075
  incremented: false,
460
1076
  argNode: valueArg
461
1077
  };
@@ -530,7 +1146,7 @@ function mergeOverlappingConditions(segments) {
530
1146
  const propToIndices = /* @__PURE__ */ new Map();
531
1147
  for (let i = 0; i < segments.length; i++) {
532
1148
  const seg = segments[i];
533
- if (seg.dynamicProps || seg.styleArrayArg || seg.whenPseudo || seg.error) continue;
1149
+ if (seg.variableProps || seg.styleArrayArg || seg.whenPseudo || seg.error) continue;
534
1150
  for (const prop of Object.keys(seg.defs)) {
535
1151
  if (!propToIndices.has(prop)) propToIndices.set(prop, []);
536
1152
  propToIndices.get(prop).push(i);
@@ -746,44 +1362,44 @@ var UnsupportedPatternError = class extends Error {
746
1362
  };
747
1363
 
748
1364
  // src/plugin/ast-utils.ts
749
- import * as t from "@babel/types";
1365
+ import * as t2 from "@babel/types";
750
1366
  function collectTopLevelBindings(ast) {
751
1367
  const used = /* @__PURE__ */ new Set();
752
1368
  for (const node of ast.program.body) {
753
- if (t.isImportDeclaration(node)) {
1369
+ if (t2.isImportDeclaration(node)) {
754
1370
  for (const spec of node.specifiers) {
755
1371
  used.add(spec.local.name);
756
1372
  }
757
1373
  continue;
758
1374
  }
759
- if (t.isVariableDeclaration(node)) {
1375
+ if (t2.isVariableDeclaration(node)) {
760
1376
  for (const decl of node.declarations) {
761
1377
  collectPatternBindings(decl.id, used);
762
1378
  }
763
1379
  continue;
764
1380
  }
765
- if (t.isFunctionDeclaration(node) && node.id) {
1381
+ if (t2.isFunctionDeclaration(node) && node.id) {
766
1382
  used.add(node.id.name);
767
1383
  continue;
768
1384
  }
769
- if (t.isClassDeclaration(node) && node.id) {
1385
+ if (t2.isClassDeclaration(node) && node.id) {
770
1386
  used.add(node.id.name);
771
1387
  continue;
772
1388
  }
773
- if (t.isExportNamedDeclaration(node) && node.declaration) {
1389
+ if (t2.isExportNamedDeclaration(node) && node.declaration) {
774
1390
  const decl = node.declaration;
775
- if (t.isVariableDeclaration(decl)) {
1391
+ if (t2.isVariableDeclaration(decl)) {
776
1392
  for (const varDecl of decl.declarations) {
777
1393
  collectPatternBindings(varDecl.id, used);
778
1394
  }
779
- } else if ((t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) && decl.id) {
1395
+ } else if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
780
1396
  used.add(decl.id.name);
781
1397
  }
782
1398
  continue;
783
1399
  }
784
- if (t.isExportDefaultDeclaration(node)) {
1400
+ if (t2.isExportDefaultDeclaration(node)) {
785
1401
  const decl = node.declaration;
786
- if ((t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) && decl.id) {
1402
+ if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
787
1403
  used.add(decl.id.name);
788
1404
  }
789
1405
  }
@@ -791,37 +1407,37 @@ function collectTopLevelBindings(ast) {
791
1407
  return used;
792
1408
  }
793
1409
  function collectPatternBindings(pattern, used) {
794
- if (t.isVoidPattern(pattern)) {
1410
+ if (t2.isVoidPattern(pattern)) {
795
1411
  return;
796
1412
  }
797
- if (t.isIdentifier(pattern)) {
1413
+ if (t2.isIdentifier(pattern)) {
798
1414
  used.add(pattern.name);
799
1415
  return;
800
1416
  }
801
- if (t.isAssignmentPattern(pattern)) {
1417
+ if (t2.isAssignmentPattern(pattern)) {
802
1418
  collectPatternBindings(pattern.left, used);
803
1419
  return;
804
1420
  }
805
- if (t.isRestElement(pattern)) {
1421
+ if (t2.isRestElement(pattern)) {
806
1422
  collectPatternBindings(pattern.argument, used);
807
1423
  return;
808
1424
  }
809
- if (t.isObjectPattern(pattern)) {
1425
+ if (t2.isObjectPattern(pattern)) {
810
1426
  for (const prop of pattern.properties) {
811
- if (t.isObjectProperty(prop)) {
1427
+ if (t2.isObjectProperty(prop)) {
812
1428
  collectPatternBindings(prop.value, used);
813
- } else if (t.isRestElement(prop)) {
1429
+ } else if (t2.isRestElement(prop)) {
814
1430
  collectPatternBindings(prop.argument, used);
815
1431
  }
816
1432
  }
817
1433
  return;
818
1434
  }
819
- if (t.isArrayPattern(pattern)) {
1435
+ if (t2.isArrayPattern(pattern)) {
820
1436
  for (const el of pattern.elements) {
821
1437
  if (!el) continue;
822
- if (t.isIdentifier(el) || t.isAssignmentPattern(el) || t.isObjectPattern(el) || t.isArrayPattern(el)) {
1438
+ if (t2.isIdentifier(el) || t2.isAssignmentPattern(el) || t2.isObjectPattern(el) || t2.isArrayPattern(el)) {
823
1439
  collectPatternBindings(el, used);
824
- } else if (t.isRestElement(el)) {
1440
+ } else if (t2.isRestElement(el)) {
825
1441
  collectPatternBindings(el.argument, used);
826
1442
  }
827
1443
  }
@@ -848,9 +1464,9 @@ function reservePreferredName(used, preferred, secondary) {
848
1464
  }
849
1465
  function findCssImportBinding(ast) {
850
1466
  for (const node of ast.program.body) {
851
- if (!t.isImportDeclaration(node)) continue;
1467
+ if (!t2.isImportDeclaration(node)) continue;
852
1468
  for (const spec of node.specifiers) {
853
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: "Css" })) {
1469
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: "Css" })) {
854
1470
  return spec.local.name;
855
1471
  }
856
1472
  }
@@ -859,9 +1475,9 @@ function findCssImportBinding(ast) {
859
1475
  }
860
1476
  function hasCssMethodCall(ast, binding, method) {
861
1477
  let found = false;
862
- t.traverseFast(ast, (node) => {
1478
+ t2.traverseFast(ast, (node) => {
863
1479
  if (found) return;
864
- if (t.isCallExpression(node) && t.isMemberExpression(node.callee) && !node.callee.computed && t.isIdentifier(node.callee.object, { name: binding }) && t.isIdentifier(node.callee.property, { name: method })) {
1480
+ if (t2.isCallExpression(node) && t2.isMemberExpression(node.callee) && !node.callee.computed && t2.isIdentifier(node.callee.object, { name: binding }) && t2.isIdentifier(node.callee.property, { name: method })) {
865
1481
  found = true;
866
1482
  }
867
1483
  });
@@ -870,8 +1486,8 @@ function hasCssMethodCall(ast, binding, method) {
870
1486
  function removeCssImport(ast, cssBinding) {
871
1487
  for (let i = 0; i < ast.program.body.length; i++) {
872
1488
  const node = ast.program.body[i];
873
- if (!t.isImportDeclaration(node)) continue;
874
- const cssSpecIndex = node.specifiers.findIndex((s) => t.isImportSpecifier(s) && s.local.name === cssBinding);
1489
+ if (!t2.isImportDeclaration(node)) continue;
1490
+ const cssSpecIndex = node.specifiers.findIndex((s) => t2.isImportSpecifier(s) && s.local.name === cssBinding);
875
1491
  if (cssSpecIndex === -1) continue;
876
1492
  if (node.specifiers.length === 1) {
877
1493
  ast.program.body.splice(i, 1);
@@ -881,40 +1497,20 @@ function removeCssImport(ast, cssBinding) {
881
1497
  return;
882
1498
  }
883
1499
  }
884
- function findStylexNamespaceImport(ast) {
885
- for (const node of ast.program.body) {
886
- if (!t.isImportDeclaration(node)) continue;
887
- if (node.source.value !== "@stylexjs/stylex") continue;
888
- for (const spec of node.specifiers) {
889
- if (t.isImportNamespaceSpecifier(spec)) {
890
- return spec.local.name;
891
- }
892
- }
893
- }
894
- return null;
895
- }
896
1500
  function findLastImportIndex(ast) {
897
1501
  let lastImportIndex = -1;
898
1502
  for (let i = 0; i < ast.program.body.length; i++) {
899
- if (t.isImportDeclaration(ast.program.body[i])) {
1503
+ if (t2.isImportDeclaration(ast.program.body[i])) {
900
1504
  lastImportIndex = i;
901
1505
  }
902
1506
  }
903
1507
  return lastImportIndex;
904
1508
  }
905
- function insertStylexNamespaceImport(ast, localName) {
906
- const stylexImport = t.importDeclaration(
907
- [t.importNamespaceSpecifier(t.identifier(localName))],
908
- t.stringLiteral("@stylexjs/stylex")
909
- );
910
- const idx = findLastImportIndex(ast);
911
- ast.program.body.splice(idx + 1, 0, stylexImport);
912
- }
913
1509
  function findNamedImportBinding(ast, source, importedName) {
914
1510
  for (const node of ast.program.body) {
915
- if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
1511
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
916
1512
  for (const spec of node.specifiers) {
917
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: importedName })) {
1513
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: importedName })) {
918
1514
  return spec.local.name;
919
1515
  }
920
1516
  }
@@ -924,21 +1520,21 @@ function findNamedImportBinding(ast, source, importedName) {
924
1520
  function upsertNamedImports(ast, source, imports) {
925
1521
  if (imports.length === 0) return;
926
1522
  for (const node of ast.program.body) {
927
- if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
1523
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
928
1524
  for (const entry of imports) {
929
1525
  const exists = node.specifiers.some(function(spec) {
930
- return t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: entry.importedName });
1526
+ return t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: entry.importedName });
931
1527
  });
932
1528
  if (exists) continue;
933
- node.specifiers.push(t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName)));
1529
+ node.specifiers.push(t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName)));
934
1530
  }
935
1531
  return;
936
1532
  }
937
- const importDecl = t.importDeclaration(
1533
+ const importDecl = t2.importDeclaration(
938
1534
  imports.map(function(entry) {
939
- return t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName));
1535
+ return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
940
1536
  }),
941
- t.stringLiteral(source)
1537
+ t2.stringLiteral(source)
942
1538
  );
943
1539
  const idx = findLastImportIndex(ast);
944
1540
  ast.program.body.splice(idx + 1, 0, importDecl);
@@ -947,11 +1543,11 @@ function extractChain(node, cssBinding) {
947
1543
  const chain = [];
948
1544
  let current = node;
949
1545
  while (true) {
950
- if (t.isIdentifier(current, { name: cssBinding })) {
1546
+ if (t2.isIdentifier(current, { name: cssBinding })) {
951
1547
  chain.reverse();
952
1548
  return chain;
953
1549
  }
954
- if (t.isMemberExpression(current) && !current.computed && t.isIdentifier(current.property)) {
1550
+ if (t2.isMemberExpression(current) && !current.computed && t2.isIdentifier(current.property)) {
955
1551
  const name = current.property.name;
956
1552
  if (name === "else") {
957
1553
  chain.push({ type: "else" });
@@ -961,7 +1557,7 @@ function extractChain(node, cssBinding) {
961
1557
  current = current.object;
962
1558
  continue;
963
1559
  }
964
- if (t.isCallExpression(current) && t.isMemberExpression(current.callee) && !current.callee.computed && t.isIdentifier(current.callee.property)) {
1560
+ if (t2.isCallExpression(current) && t2.isMemberExpression(current.callee) && !current.callee.computed && t2.isIdentifier(current.callee.property)) {
965
1561
  const name = current.callee.property.name;
966
1562
  if (name === "if") {
967
1563
  chain.push({
@@ -983,328 +1579,28 @@ function extractChain(node, cssBinding) {
983
1579
  }
984
1580
  }
985
1581
 
986
- // src/plugin/emit-stylex.ts
987
- import * as t2 from "@babel/types";
988
- function collectCreateData(chains) {
989
- const createEntries = /* @__PURE__ */ new Map();
990
- const runtimeLookups = /* @__PURE__ */ new Map();
991
- let needsMaybeInc = false;
992
- for (const chain of chains) {
993
- for (const part of chain.parts) {
994
- const segs = part.type === "unconditional" ? part.segments : [...part.thenSegments, ...part.elseSegments];
995
- for (const seg of segs) {
996
- if (seg.error) continue;
997
- if (seg.typographyLookup) {
998
- collectTypographyLookup(createEntries, runtimeLookups, seg);
999
- continue;
1000
- }
1001
- if (seg.styleArrayArg) {
1002
- continue;
1003
- }
1004
- if (seg.dynamicProps) {
1005
- if (!createEntries.has(seg.key)) {
1006
- createEntries.set(seg.key, {
1007
- key: seg.key,
1008
- dynamic: {
1009
- props: seg.dynamicProps,
1010
- extraDefs: seg.dynamicExtraDefs,
1011
- mediaQuery: seg.mediaQuery,
1012
- pseudoClass: seg.pseudoClass,
1013
- pseudoElement: seg.pseudoElement
1014
- }
1015
- });
1016
- }
1017
- } else {
1018
- if (!createEntries.has(seg.key)) {
1019
- createEntries.set(seg.key, {
1020
- key: seg.key,
1021
- defs: seg.defs,
1022
- whenPseudo: seg.whenPseudo
1023
- });
1024
- }
1025
- }
1026
- if (seg.incremented && seg.dynamicProps) {
1027
- needsMaybeInc = true;
1028
- }
1029
- }
1030
- }
1031
- }
1032
- return { createEntries, runtimeLookups, needsMaybeInc };
1033
- }
1034
- function collectTypographyLookup(createEntries, runtimeLookups, seg) {
1035
- const lookup = seg.typographyLookup;
1036
- if (!lookup) return;
1037
- if (!runtimeLookups.has(lookup.lookupKey)) {
1038
- runtimeLookups.set(lookup.lookupKey, {
1039
- lookupKey: lookup.lookupKey,
1040
- refsByName: Object.fromEntries(
1041
- Object.entries(lookup.segmentsByName).map(function([name, segments]) {
1042
- return [
1043
- name,
1044
- segments.map(function(segment) {
1045
- return segment.key;
1046
- })
1047
- ];
1048
- })
1049
- )
1050
- });
1051
- }
1052
- for (const segments of Object.values(lookup.segmentsByName)) {
1053
- for (const segment of segments) {
1054
- if (createEntries.has(segment.key)) continue;
1055
- createEntries.set(segment.key, {
1056
- key: segment.key,
1057
- defs: segment.defs,
1058
- whenPseudo: segment.whenPseudo
1059
- });
1060
- }
1061
- }
1062
- }
1063
- function buildCreateProperties(createEntries, stylexNamespaceName) {
1064
- const createProperties = [];
1065
- for (const [, entry] of createEntries) {
1066
- if (entry.dynamic) {
1067
- const paramId = t2.identifier("v");
1068
- const bodyProps = [];
1069
- const { mediaQuery, pseudoClass } = entry.dynamic;
1070
- for (const prop of entry.dynamic.props) {
1071
- if (pseudoClass && mediaQuery) {
1072
- bodyProps.push(
1073
- t2.objectProperty(
1074
- toPropertyKey(prop),
1075
- t2.objectExpression([
1076
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1077
- t2.objectProperty(
1078
- t2.stringLiteral(pseudoClass),
1079
- t2.objectExpression([
1080
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1081
- t2.objectProperty(t2.stringLiteral(mediaQuery), paramId)
1082
- ])
1083
- )
1084
- ])
1085
- )
1086
- );
1087
- } else if (pseudoClass || mediaQuery) {
1088
- const condition = pseudoClass || mediaQuery;
1089
- bodyProps.push(
1090
- t2.objectProperty(
1091
- toPropertyKey(prop),
1092
- t2.objectExpression([
1093
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1094
- t2.objectProperty(t2.stringLiteral(condition), paramId)
1095
- ])
1096
- )
1097
- );
1098
- } else {
1099
- bodyProps.push(t2.objectProperty(toPropertyKey(prop), paramId));
1100
- }
1101
- }
1102
- if (entry.dynamic.extraDefs) {
1103
- for (const [prop, value] of Object.entries(entry.dynamic.extraDefs)) {
1104
- if (pseudoClass && mediaQuery) {
1105
- bodyProps.push(
1106
- t2.objectProperty(
1107
- toPropertyKey(prop),
1108
- t2.objectExpression([
1109
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1110
- t2.objectProperty(
1111
- t2.stringLiteral(pseudoClass),
1112
- t2.objectExpression([
1113
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1114
- t2.objectProperty(t2.stringLiteral(mediaQuery), valueToAst(value))
1115
- ])
1116
- )
1117
- ])
1118
- )
1119
- );
1120
- } else if (pseudoClass || mediaQuery) {
1121
- const condition = pseudoClass || mediaQuery;
1122
- bodyProps.push(
1123
- t2.objectProperty(
1124
- toPropertyKey(prop),
1125
- t2.objectExpression([
1126
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1127
- t2.objectProperty(t2.stringLiteral(condition), valueToAst(value))
1128
- ])
1129
- )
1130
- );
1131
- } else {
1132
- bodyProps.push(t2.objectProperty(toPropertyKey(prop), valueToAst(value)));
1133
- }
1134
- }
1135
- }
1136
- let bodyExpr = t2.objectExpression(bodyProps);
1137
- if (entry.dynamic.pseudoElement) {
1138
- bodyExpr = t2.objectExpression([t2.objectProperty(t2.stringLiteral(entry.dynamic.pseudoElement), bodyExpr)]);
1139
- }
1140
- const arrowFn = t2.arrowFunctionExpression([paramId], bodyExpr);
1141
- createProperties.push(t2.objectProperty(toPropertyKey(entry.key), arrowFn));
1142
- continue;
1143
- }
1144
- if (entry.whenPseudo && entry.defs) {
1145
- const ap = entry.whenPseudo;
1146
- const props = [];
1147
- for (const [prop, value] of Object.entries(entry.defs)) {
1148
- const whenCallArgs = [t2.stringLiteral(ap.pseudo)];
1149
- if (ap.markerNode) {
1150
- whenCallArgs.push(ap.markerNode);
1151
- }
1152
- const relationship = ap.relationship ?? "ancestor";
1153
- const whenCall = t2.callExpression(
1154
- t2.memberExpression(
1155
- t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("when")),
1156
- t2.identifier(relationship)
1157
- ),
1158
- whenCallArgs
1159
- );
1160
- props.push(
1161
- t2.objectProperty(
1162
- toPropertyKey(prop),
1163
- t2.objectExpression([
1164
- t2.objectProperty(t2.identifier("default"), t2.nullLiteral()),
1165
- t2.objectProperty(whenCall, valueToAst(value), true)
1166
- ])
1167
- )
1168
- );
1169
- }
1170
- createProperties.push(t2.objectProperty(toPropertyKey(entry.key), t2.objectExpression(props)));
1171
- continue;
1172
- }
1173
- if (entry.defs) {
1174
- createProperties.push(t2.objectProperty(toPropertyKey(entry.key), defsToAst(entry.defs)));
1175
- }
1176
- }
1177
- return createProperties;
1178
- }
1179
- function buildMaybeIncDeclaration(helperName, increment) {
1180
- const incParam = t2.identifier("inc");
1181
- const body = t2.blockStatement([
1182
- t2.returnStatement(
1183
- t2.conditionalExpression(
1184
- t2.binaryExpression("===", t2.unaryExpression("typeof", incParam), t2.stringLiteral("string")),
1185
- incParam,
1186
- t2.templateLiteral(
1187
- [t2.templateElement({ raw: "", cooked: "" }, false), t2.templateElement({ raw: "px", cooked: "px" }, true)],
1188
- [t2.binaryExpression("*", incParam, t2.numericLiteral(increment))]
1189
- )
1190
- )
1191
- )
1192
- ]);
1193
- return t2.variableDeclaration("const", [
1194
- t2.variableDeclarator(t2.identifier(helperName), t2.arrowFunctionExpression([incParam], body))
1195
- ]);
1196
- }
1197
- function buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties) {
1198
- const createCall = t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("create")), [
1199
- t2.objectExpression(createProperties)
1200
- ]);
1201
- return t2.variableDeclaration("const", [t2.variableDeclarator(t2.identifier(createVarName), createCall)]);
1202
- }
1203
- function buildRuntimeLookupDeclaration(lookupName, createVarName, lookup) {
1204
- const properties = [];
1205
- for (const [name, refs] of Object.entries(lookup.refsByName)) {
1206
- const values = refs.map(function(refKey) {
1207
- return t2.memberExpression(t2.identifier(createVarName), t2.identifier(refKey));
1208
- });
1209
- properties.push(t2.objectProperty(toPropertyKey(name), t2.arrayExpression(values)));
1210
- }
1211
- return t2.variableDeclaration("const", [
1212
- t2.variableDeclarator(t2.identifier(lookupName), t2.objectExpression(properties))
1213
- ]);
1214
- }
1215
- function defsToAst(defs) {
1216
- const properties = [];
1217
- for (const [key, value] of Object.entries(defs)) {
1218
- const keyNode = toPropertyKey(key);
1219
- if (value === null) {
1220
- properties.push(t2.objectProperty(keyNode, t2.nullLiteral()));
1221
- } else if (typeof value === "string") {
1222
- properties.push(t2.objectProperty(keyNode, t2.stringLiteral(value)));
1223
- } else if (typeof value === "number") {
1224
- properties.push(t2.objectProperty(keyNode, t2.numericLiteral(value)));
1225
- } else if (typeof value === "object") {
1226
- properties.push(t2.objectProperty(keyNode, defsToAst(value)));
1227
- }
1228
- }
1229
- return t2.objectExpression(properties);
1230
- }
1231
- function valueToAst(value) {
1232
- if (value === null) return t2.nullLiteral();
1233
- if (typeof value === "string") return t2.stringLiteral(value);
1234
- if (typeof value === "number") return t2.numericLiteral(value);
1235
- if (typeof value === "object") return defsToAst(value);
1236
- return t2.stringLiteral(String(value));
1237
- }
1238
- function toPropertyKey(key) {
1239
- return isValidIdentifier(key) ? t2.identifier(key) : t2.stringLiteral(key);
1240
- }
1241
- function isValidIdentifier(s) {
1242
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
1243
- }
1244
-
1245
1582
  // src/plugin/rewrite-sites.ts
1246
1583
  import _traverse from "@babel/traverse";
1247
1584
  import _generate from "@babel/generator";
1248
1585
  import * as t3 from "@babel/types";
1249
1586
  var generate = _generate.default ?? _generate;
1250
1587
  var traverse = _traverse.default ?? _traverse;
1251
- function formatDroppedPropertyKey(prop) {
1252
- if (t3.isObjectProperty(prop)) {
1253
- if (t3.isIdentifier(prop.key)) return prop.key.name;
1254
- if (t3.isStringLiteral(prop.key)) return prop.key.value;
1255
- }
1256
- return formatNodeSnippet(prop);
1257
- }
1258
1588
  function rewriteExpressionSites(options) {
1259
1589
  for (const site of options.sites) {
1260
- const propsArgs = buildPropsArgsFromChain(site.resolvedChain, options);
1590
+ const styleHash = buildStyleHashFromChain(site.resolvedChain, options);
1261
1591
  const cssAttrPath = getCssAttributePath(site.path);
1592
+ const line = site.path.node.loc?.start.line ?? null;
1262
1593
  if (cssAttrPath) {
1263
- cssAttrPath.replaceWith(
1264
- t3.jsxSpreadAttribute(
1265
- buildCssSpreadExpression(
1266
- cssAttrPath,
1267
- propsArgs,
1268
- site.path.node.loc?.start.line ?? null,
1269
- options.mergePropsHelperName,
1270
- options.needsMergePropsHelper,
1271
- options
1272
- )
1273
- )
1274
- );
1275
- continue;
1594
+ cssAttrPath.replaceWith(t3.jsxSpreadAttribute(buildCssSpreadExpression(cssAttrPath, styleHash, line, options)));
1595
+ } else {
1596
+ if (options.debug && line !== null) {
1597
+ injectDebugInfo(styleHash, line, options);
1598
+ }
1599
+ site.path.replaceWith(styleHash);
1276
1600
  }
1277
- site.path.replaceWith(buildStyleArrayExpression(propsArgs, site.path.node.loc?.start.line ?? null, options));
1278
1601
  }
1279
1602
  rewriteCssPropsCalls(options);
1280
- rewriteCssSpreadCalls(
1281
- options.ast,
1282
- options.cssBindingName,
1283
- options.asStyleArrayHelperName,
1284
- options.needsAsStyleArrayHelper
1285
- );
1286
- rewriteStyleObjectExpressions(
1287
- options.ast,
1288
- options.skippedCssPropMessages,
1289
- options.asStyleArrayHelperName,
1290
- options.needsAsStyleArrayHelper
1291
- );
1292
- normalizeMixedStyleTernaries(options.ast);
1293
- rewriteCssAttributeExpressions(
1294
- options.ast,
1295
- options.filename,
1296
- options.debug,
1297
- options.stylexNamespaceName,
1298
- options.mergePropsHelperName,
1299
- options.needsMergePropsHelper,
1300
- options.trussPropsHelperName,
1301
- options.needsTrussPropsHelper,
1302
- options.trussDebugInfoName,
1303
- options.needsTrussDebugInfo,
1304
- options.asStyleArrayHelperName,
1305
- options.needsAsStyleArrayHelper,
1306
- options.skippedCssPropMessages
1307
- );
1603
+ rewriteCssAttributeExpressions(options);
1308
1604
  }
1309
1605
  function getCssAttributePath(path) {
1310
1606
  const parentPath = path.parentPath;
@@ -1314,312 +1610,120 @@ function getCssAttributePath(path) {
1314
1610
  if (!t3.isJSXIdentifier(attrPath.node.name, { name: "css" })) return null;
1315
1611
  return attrPath;
1316
1612
  }
1317
- function buildPropsArgsFromChain(chain, options) {
1318
- const args = [];
1319
- for (const marker of chain.markers) {
1320
- if (marker.markerNode) {
1321
- args.push(marker.markerNode);
1322
- } else {
1323
- args.push(
1324
- t3.callExpression(
1325
- t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("defaultMarker")),
1326
- []
1327
- )
1328
- );
1329
- }
1613
+ function buildStyleHashFromChain(chain, options) {
1614
+ const members = [];
1615
+ if (chain.markers.length > 0) {
1616
+ const markerClasses = chain.markers.map(function(marker) {
1617
+ return markerClassName(marker.markerNode);
1618
+ });
1619
+ members.push(t3.objectProperty(t3.identifier("__marker"), t3.stringLiteral(markerClasses.join(" "))));
1330
1620
  }
1331
1621
  for (const part of chain.parts) {
1332
1622
  if (part.type === "unconditional") {
1333
- args.push(...buildPropsArgs(part.segments, options));
1334
- continue;
1335
- }
1336
- const thenArgs = buildPropsArgs(part.thenSegments, options);
1337
- const elseArgs = buildPropsArgs(part.elseSegments, options);
1338
- if (thenArgs.length === 1 && elseArgs.length === 1 && !t3.isSpreadElement(thenArgs[0]) && !t3.isSpreadElement(elseArgs[0])) {
1339
- args.push(t3.conditionalExpression(part.conditionNode, thenArgs[0], elseArgs[0]));
1340
- } else if (thenArgs.length > 0 || elseArgs.length > 0) {
1341
- args.push(
1623
+ members.push(...buildStyleHashMembers(part.segments, options));
1624
+ } else {
1625
+ const thenMembers = buildStyleHashMembers(part.thenSegments, options);
1626
+ const elseMembers = buildStyleHashMembers(part.elseSegments, options);
1627
+ members.push(
1342
1628
  t3.spreadElement(
1343
- t3.conditionalExpression(part.conditionNode, t3.arrayExpression(thenArgs), t3.arrayExpression(elseArgs))
1629
+ t3.conditionalExpression(part.conditionNode, t3.objectExpression(thenMembers), t3.objectExpression(elseMembers))
1344
1630
  )
1345
1631
  );
1346
1632
  }
1347
1633
  }
1348
- return args;
1634
+ return t3.objectExpression(members);
1349
1635
  }
1350
- function buildPropsArgs(segments, options) {
1351
- const args = [];
1636
+ function buildStyleHashMembers(segments, options) {
1637
+ const members = [];
1638
+ const normalSegs = [];
1639
+ function flushNormal() {
1640
+ if (normalSegs.length > 0) {
1641
+ members.push(...buildStyleHashProperties(normalSegs, options.mapping, options.maybeIncHelperName));
1642
+ normalSegs.length = 0;
1643
+ }
1644
+ }
1352
1645
  for (const seg of segments) {
1353
1646
  if (seg.error) continue;
1354
- if (seg.typographyLookup) {
1355
- const lookupName = options.runtimeLookupNames.get(seg.typographyLookup.lookupKey);
1356
- if (!lookupName) {
1357
- continue;
1358
- }
1359
- const lookupAccess = t3.memberExpression(
1360
- t3.identifier(lookupName),
1361
- seg.typographyLookup.argNode,
1362
- true
1363
- );
1364
- args.push(t3.spreadElement(t3.logicalExpression("??", lookupAccess, t3.arrayExpression([]))));
1365
- continue;
1366
- }
1367
1647
  if (seg.styleArrayArg) {
1368
- args.push(
1369
- t3.spreadElement(
1370
- buildUnknownSpreadFallback(
1371
- seg.styleArrayArg,
1372
- options.asStyleArrayHelperName,
1373
- options.needsAsStyleArrayHelper
1374
- )
1375
- )
1376
- );
1648
+ flushNormal();
1649
+ members.push(t3.spreadElement(seg.styleArrayArg));
1377
1650
  continue;
1378
1651
  }
1379
- const ref = t3.memberExpression(t3.identifier(options.createVarName), t3.identifier(seg.key));
1380
- if (seg.dynamicProps && seg.argNode) {
1381
- let argExpr;
1382
- if (seg.incremented && options.maybeIncHelperName) {
1383
- argExpr = t3.callExpression(t3.identifier(options.maybeIncHelperName), [seg.argNode]);
1384
- } else if (seg.incremented) {
1385
- argExpr = seg.argNode;
1386
- } else if (seg.appendPx) {
1387
- argExpr = t3.binaryExpression(
1388
- "+",
1389
- t3.callExpression(t3.identifier("String"), [seg.argNode]),
1390
- t3.stringLiteral("px")
1652
+ if (seg.typographyLookup) {
1653
+ flushNormal();
1654
+ const lookupName = options.runtimeLookupNames.get(seg.typographyLookup.lookupKey);
1655
+ if (lookupName) {
1656
+ const lookupAccess = t3.memberExpression(
1657
+ t3.identifier(lookupName),
1658
+ seg.typographyLookup.argNode,
1659
+ true
1391
1660
  );
1392
- } else {
1393
- argExpr = t3.callExpression(t3.identifier("String"), [seg.argNode]);
1661
+ members.push(t3.spreadElement(t3.logicalExpression("??", lookupAccess, t3.objectExpression([]))));
1394
1662
  }
1395
- args.push(t3.callExpression(ref, [argExpr]));
1396
- } else {
1397
- args.push(ref);
1663
+ continue;
1398
1664
  }
1665
+ normalSegs.push(seg);
1399
1666
  }
1400
- return args;
1667
+ flushNormal();
1668
+ return members;
1401
1669
  }
1402
- function rewriteCssAttributeExpressions(ast, filename, debug, stylexNamespaceName, mergePropsHelperName, needsMergePropsHelper, trussPropsHelperName, needsTrussPropsHelper, trussDebugInfoName, needsTrussDebugInfo, asStyleArrayHelperName, needsAsStyleArrayHelper, skippedCssPropMessages) {
1403
- traverse(ast, {
1404
- JSXAttribute(path) {
1405
- if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1406
- const value = path.node.value;
1407
- if (!t3.isJSXExpressionContainer(value)) return;
1408
- if (!t3.isExpression(value.expression)) return;
1409
- const propsArgs = lowerCssExpressionToPropsArgs(
1410
- value.expression,
1411
- path,
1412
- asStyleArrayHelperName,
1413
- needsAsStyleArrayHelper
1414
- );
1415
- if (!propsArgs) {
1416
- skippedCssPropMessages.push({
1417
- message: explainSkippedCssRewrite(value.expression, path),
1418
- line: path.node.loc?.start.line ?? null
1419
- });
1420
- return;
1421
- }
1422
- path.replaceWith(
1423
- t3.jsxSpreadAttribute(
1424
- buildCssSpreadExpression(
1425
- path,
1426
- propsArgs,
1427
- path.node.loc?.start.line ?? null,
1428
- mergePropsHelperName,
1429
- needsMergePropsHelper,
1430
- {
1431
- filename,
1432
- debug,
1433
- stylexNamespaceName,
1434
- trussPropsHelperName,
1435
- needsTrussPropsHelper,
1436
- trussDebugInfoName,
1437
- needsTrussDebugInfo
1438
- }
1439
- )
1440
- )
1441
- );
1442
- }
1670
+ function injectDebugInfo(expr, line, options) {
1671
+ if (!options.debug) return;
1672
+ const firstProp = expr.properties.find(function(p) {
1673
+ return t3.isObjectProperty(p) && !(t3.isIdentifier(p.key) && p.key.name === "__marker" || t3.isStringLiteral(p.key) && p.key.value === "__marker");
1443
1674
  });
1444
- }
1445
- function buildStyleArrayExpression(propsArgs, line, options) {
1446
- const elements = buildDebugElements(line, options);
1447
- elements.push(...propsArgs);
1448
- return t3.arrayExpression(elements);
1449
- }
1450
- function buildPropsCall(propsArgs, line, options) {
1451
- if (!options.debug) {
1452
- return t3.callExpression(
1453
- t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("props")),
1454
- propsArgs
1455
- );
1456
- }
1457
- options.needsTrussPropsHelper.current = true;
1458
- const args = buildDebugElements(line, options);
1459
- args.push(...propsArgs);
1460
- return t3.callExpression(t3.identifier(options.trussPropsHelperName), [
1461
- t3.identifier(options.stylexNamespaceName),
1462
- ...args
1675
+ if (!firstProp) return;
1676
+ options.needsTrussDebugInfo.current = true;
1677
+ const debugExpr = t3.newExpression(t3.identifier(options.trussDebugInfoName), [
1678
+ t3.stringLiteral(`${options.filename}:${line}`)
1463
1679
  ]);
1464
- }
1465
- function buildDebugElements(line, options) {
1466
- if (!options.debug || line === null) {
1467
- return [];
1680
+ if (t3.isStringLiteral(firstProp.value)) {
1681
+ firstProp.value = t3.arrayExpression([firstProp.value, debugExpr]);
1682
+ } else if (t3.isArrayExpression(firstProp.value)) {
1683
+ firstProp.value.elements.push(debugExpr);
1468
1684
  }
1469
- options.needsTrussDebugInfo.current = true;
1470
- return [t3.newExpression(t3.identifier(options.trussDebugInfoName), [t3.stringLiteral(`${options.filename}:${line}`)])];
1471
- }
1472
- function lowerCssExpressionToPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1473
- return buildStyleObjectPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) ?? buildStyleArrayLikePropsArgsFromExpression(expr, path);
1474
1685
  }
1475
- function explainSkippedCssRewrite(expr, path) {
1476
- if (t3.isObjectExpression(expr)) {
1477
- for (const prop of expr.properties) {
1478
- if (!t3.isSpreadElement(prop)) {
1479
- return `[truss] Unsupported pattern: Could not rewrite css prop: object contains a non-spread property (${formatNodeSnippet(expr)})`;
1480
- }
1481
- const normalizedArg = normalizeStyleExpression(prop.argument);
1482
- if (!normalizedArg) {
1483
- return `[truss] Unsupported pattern: Could not rewrite css prop: spread argument is not style-array-like (${formatNodeSnippet(prop.argument)})`;
1484
- }
1485
- }
1486
- return `[truss] Unsupported pattern: Could not rewrite css prop: object spread composition was not recognized (${formatNodeSnippet(expr)})`;
1686
+ function buildCssSpreadExpression(path, styleHash, line, options) {
1687
+ const existingClassNameExpr = removeExistingAttribute(path, "className");
1688
+ const existingStyleExpr = removeExistingAttribute(path, "style");
1689
+ if (!existingClassNameExpr && !existingStyleExpr) {
1690
+ return buildPropsCall(styleHash, line, options);
1487
1691
  }
1488
- return `[truss] Unsupported pattern: Could not rewrite css prop: expression is not style-array-like (${formatNodeSnippet(expr)})`;
1489
- }
1490
- function formatNodeSnippet(node) {
1491
- return generate(node, { compact: true, comments: true }).code;
1492
- }
1493
- function buildCssSpreadExpression(path, propsArgs, line, mergePropsHelperName, needsMergePropsHelper, options) {
1494
- const existingClassNameExpr = removeExistingClassNameAttribute(path);
1495
- if (!existingClassNameExpr) return buildPropsCall(propsArgs, line, options);
1496
- needsMergePropsHelper.current = true;
1497
- const args = buildDebugElements(line, options);
1498
- args.push(...propsArgs);
1499
- return t3.callExpression(t3.identifier(mergePropsHelperName), [
1500
- t3.identifier(options.stylexNamespaceName),
1501
- existingClassNameExpr,
1502
- ...args
1692
+ options.needsMergePropsHelper.current = true;
1693
+ if (options.debug && line !== null) {
1694
+ injectDebugInfo(styleHash, line, options);
1695
+ }
1696
+ return t3.callExpression(t3.identifier(options.mergePropsHelperName), [
1697
+ existingClassNameExpr ?? t3.identifier("undefined"),
1698
+ existingStyleExpr ?? t3.identifier("undefined"),
1699
+ styleHash
1503
1700
  ]);
1504
1701
  }
1505
- function removeExistingClassNameAttribute(path) {
1702
+ function buildPropsCall(styleHash, line, options) {
1703
+ options.needsTrussPropsHelper.current = true;
1704
+ if (options.debug && line !== null && t3.isObjectExpression(styleHash)) {
1705
+ injectDebugInfo(styleHash, line, options);
1706
+ }
1707
+ return t3.callExpression(t3.identifier(options.trussPropsHelperName), [styleHash]);
1708
+ }
1709
+ function removeExistingAttribute(path, attrName) {
1506
1710
  const openingElement = path.parentPath;
1507
1711
  if (!openingElement || !openingElement.isJSXOpeningElement()) return null;
1508
1712
  const attrs = openingElement.node.attributes;
1509
1713
  for (let i = 0; i < attrs.length; i++) {
1510
1714
  const attr = attrs[i];
1511
- if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: "className" })) continue;
1512
- let classNameExpr = null;
1715
+ if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: attrName })) continue;
1716
+ let expr = null;
1513
1717
  if (t3.isStringLiteral(attr.value)) {
1514
- classNameExpr = attr.value;
1718
+ expr = attr.value;
1515
1719
  } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1516
- classNameExpr = attr.value.expression;
1720
+ expr = attr.value.expression;
1517
1721
  }
1518
1722
  attrs.splice(i, 1);
1519
- return classNameExpr;
1520
- }
1521
- return null;
1522
- }
1523
- function buildStyleObjectPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1524
- if (!t3.isObjectExpression(expr) || expr.properties.length === 0) return null;
1525
- const allSpreads = expr.properties.every(function(prop) {
1526
- return t3.isSpreadElement(prop);
1527
- });
1528
- if (!allSpreads) return null;
1529
- if (hasStyleArraySpread(expr, path)) {
1530
- const result = flattenStyleObject(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper);
1531
- return result.elements.filter(Boolean);
1532
- }
1533
- return expr.properties.map(function(prop) {
1534
- const spread = prop;
1535
- return t3.spreadElement(
1536
- buildUnknownSpreadFallback(spread.argument, asStyleArrayHelperName, needsAsStyleArrayHelper)
1537
- // I.e. `css={{ ...css }}` or `css={{ ...xss }}`
1538
- );
1539
- });
1540
- }
1541
- function buildStyleArrayLikePropsArgsFromExpression(expr, path) {
1542
- const normalizedExpr = normalizeStyleExpression(expr);
1543
- if (!normalizedExpr) return null;
1544
- return buildStyleArrayLikePropsArgs(normalizedExpr, path);
1545
- }
1546
- function buildStyleArrayLikePropsArgs(expr, path) {
1547
- if (t3.isArrayExpression(expr)) {
1548
- const propsArgs = [];
1549
- for (const el of expr.elements) {
1550
- if (!el) continue;
1551
- if (t3.isSpreadElement(el)) {
1552
- const normalizedArg = normalizeStyleExpression(el.argument);
1553
- if (!normalizedArg) {
1554
- propsArgs.push(t3.spreadElement(el.argument));
1555
- continue;
1556
- }
1557
- if (t3.isArrayExpression(normalizedArg)) {
1558
- const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path);
1559
- if (nestedArgs) {
1560
- propsArgs.push(...nestedArgs);
1561
- continue;
1562
- }
1563
- }
1564
- propsArgs.push(t3.spreadElement(buildSafeSpreadArgument(normalizedArg)));
1565
- continue;
1566
- }
1567
- propsArgs.push(el);
1568
- }
1569
- return propsArgs;
1570
- }
1571
- if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isConditionalExpression(expr) || t3.isLogicalExpression(expr) || t3.isCallExpression(expr)) {
1572
- return [t3.spreadElement(buildSafeSpreadArgument(expr))];
1723
+ return expr;
1573
1724
  }
1574
1725
  return null;
1575
1726
  }
1576
- function rewriteStyleObjectExpressions(ast, messages, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1577
- traverse(ast, {
1578
- ObjectExpression(path) {
1579
- if (!hasStyleArraySpread(path.node, path)) return;
1580
- const result = flattenStyleObject(path.node, path, asStyleArrayHelperName, needsAsStyleArrayHelper);
1581
- if (result.droppedPropertyKeys.length > 0) {
1582
- messages.push({
1583
- message: `[truss] Unsupported pattern: Dropped non-spread properties from style composition object (${result.droppedPropertyKeys.join(", ")})`,
1584
- line: path.node.loc?.start.line ?? null
1585
- });
1586
- }
1587
- path.replaceWith(t3.arrayExpression(result.elements));
1588
- }
1589
- });
1590
- }
1591
- function normalizeMixedStyleTernaries(ast) {
1592
- traverse(ast, {
1593
- ConditionalExpression(path) {
1594
- const consequentHasArray = expressionContainsArray(path.node.consequent, path);
1595
- const alternateHasArray = expressionContainsArray(path.node.alternate, path);
1596
- if (consequentHasArray && isEmptyObjectExpression(path.node.alternate)) {
1597
- path.node.alternate = t3.arrayExpression([]);
1598
- } else if (alternateHasArray && isEmptyObjectExpression(path.node.consequent)) {
1599
- path.node.consequent = t3.arrayExpression([]);
1600
- }
1601
- },
1602
- LogicalExpression(path) {
1603
- if (path.node.operator !== "||" && path.node.operator !== "??") return;
1604
- if (expressionContainsArray(path.node.left, path) && isEmptyObjectExpression(path.node.right)) {
1605
- path.node.right = t3.arrayExpression([]);
1606
- }
1607
- }
1608
- });
1609
- }
1610
- function rewriteCssSpreadCalls(ast, cssBindingName, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1611
- traverse(ast, {
1612
- CallExpression(path) {
1613
- if (!isCssSpreadCall(path.node, cssBindingName)) return;
1614
- const arg = path.node.arguments[0];
1615
- if (!arg || t3.isSpreadElement(arg) || !t3.isExpression(arg) || path.node.arguments.length !== 1) return;
1616
- const styleObject = unwrapStyleObjectExpression(arg);
1617
- if (!styleObject) return;
1618
- const result = flattenStyleObject(styleObject, path, asStyleArrayHelperName, needsAsStyleArrayHelper);
1619
- path.replaceWith(t3.arrayExpression(result.elements));
1620
- }
1621
- });
1622
- }
1623
1727
  function rewriteCssPropsCalls(options) {
1624
1728
  traverse(options.ast, {
1625
1729
  CallExpression(path) {
@@ -1627,28 +1731,51 @@ function rewriteCssPropsCalls(options) {
1627
1731
  const arg = path.node.arguments[0];
1628
1732
  if (!arg || t3.isSpreadElement(arg) || !t3.isExpression(arg) || path.node.arguments.length !== 1) return;
1629
1733
  const line = path.node.loc?.start.line ?? null;
1630
- let propsArgs;
1631
- const styleObject = unwrapStyleObjectExpression(arg);
1632
- if (styleObject) {
1633
- const result = flattenStyleObject(
1634
- styleObject,
1635
- path,
1636
- options.asStyleArrayHelperName,
1637
- options.needsAsStyleArrayHelper
1734
+ options.needsTrussPropsHelper.current = true;
1735
+ const classNameExpr = extractSiblingClassName(path);
1736
+ if (classNameExpr) {
1737
+ options.needsMergePropsHelper.current = true;
1738
+ path.replaceWith(
1739
+ t3.callExpression(t3.identifier(options.mergePropsHelperName), [classNameExpr, t3.identifier("undefined"), arg])
1638
1740
  );
1639
- propsArgs = result.elements.filter((e) => e !== null);
1640
1741
  } else {
1641
- propsArgs = [t3.spreadElement(arg)];
1742
+ path.replaceWith(t3.callExpression(t3.identifier(options.trussPropsHelperName), [arg]));
1642
1743
  }
1643
- const classNameExpr = extractSiblingClassName(path);
1644
- if (classNameExpr) {
1645
- path.replaceWith(buildMergePropsCall(propsArgs, classNameExpr, line, options));
1744
+ }
1745
+ });
1746
+ }
1747
+ function rewriteCssAttributeExpressions(options) {
1748
+ traverse(options.ast, {
1749
+ JSXAttribute(path) {
1750
+ if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1751
+ const value = path.node.value;
1752
+ if (!t3.isJSXExpressionContainer(value)) return;
1753
+ if (!t3.isExpression(value.expression)) return;
1754
+ const expr = value.expression;
1755
+ const line = path.node.loc?.start.line ?? null;
1756
+ const existingClassNameExpr = removeExistingAttribute(path, "className");
1757
+ const existingStyleExpr = removeExistingAttribute(path, "style");
1758
+ if (existingClassNameExpr || existingStyleExpr) {
1759
+ options.needsMergePropsHelper.current = true;
1760
+ path.replaceWith(
1761
+ t3.jsxSpreadAttribute(
1762
+ t3.callExpression(t3.identifier(options.mergePropsHelperName), [
1763
+ existingClassNameExpr ?? t3.identifier("undefined"),
1764
+ existingStyleExpr ?? t3.identifier("undefined"),
1765
+ expr
1766
+ ])
1767
+ )
1768
+ );
1646
1769
  } else {
1647
- path.replaceWith(buildPropsCall(propsArgs, line, options));
1770
+ options.needsTrussPropsHelper.current = true;
1771
+ path.replaceWith(t3.jsxSpreadAttribute(t3.callExpression(t3.identifier(options.trussPropsHelperName), [expr])));
1648
1772
  }
1649
1773
  }
1650
1774
  });
1651
1775
  }
1776
+ function isCssPropsCall(expr, cssBindingName) {
1777
+ return t3.isMemberExpression(expr.callee) && !expr.callee.computed && t3.isIdentifier(expr.callee.object, { name: cssBindingName }) && t3.isIdentifier(expr.callee.property, { name: "props" });
1778
+ }
1652
1779
  function extractSiblingClassName(callPath) {
1653
1780
  const spreadPath = callPath.parentPath;
1654
1781
  if (!spreadPath || !spreadPath.isSpreadElement()) return null;
@@ -1666,230 +1793,9 @@ function extractSiblingClassName(callPath) {
1666
1793
  }
1667
1794
  return null;
1668
1795
  }
1669
- function buildMergePropsCall(propsArgs, classNameExpr, line, options) {
1670
- options.needsMergePropsHelper.current = true;
1671
- const args = buildDebugElements(line, options);
1672
- args.push(...propsArgs);
1673
- return t3.callExpression(t3.identifier(options.mergePropsHelperName), [
1674
- t3.identifier(options.stylexNamespaceName),
1675
- classNameExpr,
1676
- ...args
1677
- ]);
1678
- }
1679
- function flattenStyleObject(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1680
- const elements = [];
1681
- const droppedPropertyKeys = [];
1682
- for (const prop of expr.properties) {
1683
- if (!t3.isSpreadElement(prop)) {
1684
- droppedPropertyKeys.push(formatDroppedPropertyKey(prop));
1685
- continue;
1686
- }
1687
- const normalized = normalizeStyleExpression(prop.argument);
1688
- if (!normalized) {
1689
- elements.push(
1690
- t3.spreadElement(buildUnknownSpreadFallback(prop.argument, asStyleArrayHelperName, needsAsStyleArrayHelper))
1691
- );
1692
- continue;
1693
- }
1694
- if (t3.isArrayExpression(normalized)) {
1695
- elements.push(...normalized.elements);
1696
- } else if (isProvablyArray(normalized)) {
1697
- elements.push(t3.spreadElement(buildSafeSpreadArgument(normalized)));
1698
- } else {
1699
- elements.push(
1700
- t3.spreadElement(buildUnknownSpreadFallback(normalized, asStyleArrayHelperName, needsAsStyleArrayHelper))
1701
- // I.e. `...borderBottomStyles`
1702
- );
1703
- }
1704
- }
1705
- return { elements, droppedPropertyKeys };
1706
- }
1707
- function hasStyleArraySpread(expr, path) {
1708
- return expr.properties.some(function(prop) {
1709
- return t3.isSpreadElement(prop) && expressionContainsArray(prop.argument, path);
1710
- });
1711
- }
1712
- function expressionContainsArray(expr, path) {
1713
- if (t3.isArrayExpression(expr)) return true;
1714
- if (t3.isConditionalExpression(expr)) {
1715
- return expressionContainsArray(expr.consequent, path) || expressionContainsArray(expr.alternate, path);
1716
- }
1717
- if (t3.isLogicalExpression(expr)) {
1718
- return expressionContainsArray(expr.left, path) || expressionContainsArray(expr.right, path);
1719
- }
1720
- if (t3.isObjectExpression(expr)) {
1721
- return expr.properties.some(function(p) {
1722
- return t3.isSpreadElement(p) && expressionContainsArray(p.argument, path);
1723
- });
1724
- }
1725
- if (t3.isIdentifier(expr)) {
1726
- const binding = path.scope.getBinding(expr.name);
1727
- if (binding?.path.isVariableDeclarator()) {
1728
- const init = binding.path.node.init;
1729
- if (init) return expressionContainsArray(init, binding.path);
1730
- }
1731
- return false;
1732
- }
1733
- if (t3.isMemberExpression(expr) && t3.isIdentifier(expr.object)) {
1734
- const binding = path.scope.getBinding(expr.object.name);
1735
- if (binding?.path.isVariableDeclarator()) {
1736
- const init = binding.path.node.init;
1737
- if (init && t3.isObjectExpression(init)) {
1738
- const propName = getStaticMemberPropertyName(expr, path);
1739
- if (propName) {
1740
- for (const prop of init.properties) {
1741
- if (!t3.isObjectProperty(prop) || prop.computed) continue;
1742
- if (!isMatchingPropertyName(prop.key, propName)) continue;
1743
- if (t3.isExpression(prop.value)) {
1744
- return expressionContainsArray(prop.value, binding.path);
1745
- }
1746
- }
1747
- }
1748
- }
1749
- }
1750
- return false;
1751
- }
1752
- if (t3.isCallExpression(expr)) {
1753
- const returnExpr = getCallReturnExpression(expr, path);
1754
- return returnExpr ? expressionContainsArray(returnExpr, path) : false;
1755
- }
1756
- return false;
1757
- }
1758
- function normalizeStyleExpression(expr) {
1759
- if (t3.isArrayExpression(expr)) return expr;
1760
- if (isEmptyStyleFallbackExpression(expr)) return t3.arrayExpression([]);
1761
- if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1762
- const consequent = normalizeStyleExpression(expr.right);
1763
- if (!consequent) return null;
1764
- return t3.conditionalExpression(expr.left, consequent, t3.arrayExpression([]));
1765
- }
1766
- if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1767
- const left = normalizeStyleExpression(expr.left);
1768
- const right = normalizeStyleBranch(expr.right);
1769
- if (!left || !right) return null;
1770
- return t3.logicalExpression(expr.operator, left, right);
1771
- }
1772
- if (t3.isConditionalExpression(expr)) {
1773
- const consequent = normalizeStyleBranch(expr.consequent);
1774
- const alternate = normalizeStyleBranch(expr.alternate);
1775
- if (!consequent || !alternate) return null;
1776
- return t3.conditionalExpression(expr.test, consequent, alternate);
1777
- }
1778
- if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isCallExpression(expr)) {
1779
- return expr;
1780
- }
1781
- return null;
1782
- }
1783
- function normalizeStyleBranch(expr) {
1784
- if (isEmptyStyleFallbackExpression(expr)) return t3.arrayExpression([]);
1785
- if (t3.isObjectExpression(expr)) {
1786
- if (expr.properties.length === 0) return t3.arrayExpression([]);
1787
- const allSpreads = expr.properties.every(function(p) {
1788
- return t3.isSpreadElement(p);
1789
- });
1790
- if (!allSpreads) return null;
1791
- const elements = [];
1792
- for (const prop of expr.properties) {
1793
- const spread = prop;
1794
- const normalized = normalizeStyleExpression(spread.argument);
1795
- if (!normalized) return null;
1796
- if (t3.isArrayExpression(normalized)) {
1797
- elements.push(...normalized.elements);
1798
- } else {
1799
- elements.push(t3.spreadElement(buildSafeSpreadArgument(normalized)));
1800
- }
1801
- }
1802
- return t3.arrayExpression(elements);
1803
- }
1804
- return normalizeStyleExpression(expr);
1805
- }
1806
- function isCssPropsCall(expr, cssBindingName) {
1807
- return t3.isMemberExpression(expr.callee) && !expr.callee.computed && t3.isIdentifier(expr.callee.object, { name: cssBindingName }) && t3.isIdentifier(expr.callee.property, { name: "props" });
1808
- }
1809
- function isCssSpreadCall(expr, cssBindingName) {
1810
- return t3.isMemberExpression(expr.callee) && !expr.callee.computed && t3.isIdentifier(expr.callee.object, { name: cssBindingName }) && t3.isIdentifier(expr.callee.property, { name: "spread" });
1811
- }
1812
- function unwrapStyleObjectExpression(expr) {
1813
- if (t3.isObjectExpression(expr)) return expr;
1814
- if (t3.isTSAsExpression(expr) || t3.isTSSatisfiesExpression(expr) || t3.isTSNonNullExpression(expr)) {
1815
- return unwrapStyleObjectExpression(expr.expression);
1816
- }
1817
- return null;
1818
- }
1819
1796
  function isMatchingPropertyName(key, name) {
1820
1797
  return t3.isIdentifier(key) && key.name === name || t3.isStringLiteral(key) && key.value === name;
1821
1798
  }
1822
- function isEmptyObjectExpression(expr) {
1823
- return t3.isObjectExpression(expr) && expr.properties.length === 0;
1824
- }
1825
- function isEmptyStyleFallbackExpression(expr) {
1826
- return isEmptyObjectExpression(expr) || t3.isIdentifier(expr, { name: "undefined" }) || t3.isNullLiteral(expr) || t3.isBooleanLiteral(expr) && expr.value === false;
1827
- }
1828
- function isProvablyArray(expr) {
1829
- if (t3.isArrayExpression(expr)) return true;
1830
- if (t3.isParenthesizedExpression(expr)) return isProvablyArray(expr.expression);
1831
- if (t3.isConditionalExpression(expr)) {
1832
- return isProvablyArray(expr.consequent) && isProvablyArray(expr.alternate);
1833
- }
1834
- if (t3.isLogicalExpression(expr)) {
1835
- return isProvablyArray(expr.left) && isProvablyArray(expr.right);
1836
- }
1837
- return false;
1838
- }
1839
- function buildUnknownSpreadFallback(expr, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1840
- needsAsStyleArrayHelper.current = true;
1841
- return t3.callExpression(t3.identifier(asStyleArrayHelperName), [expr]);
1842
- }
1843
- function buildSafeSpreadArgument(expr) {
1844
- return t3.isConditionalExpression(expr) || t3.isLogicalExpression(expr) ? t3.parenthesizedExpression(expr) : expr;
1845
- }
1846
- function getStaticMemberPropertyName(expr, path) {
1847
- if (!expr.computed && t3.isIdentifier(expr.property)) {
1848
- return expr.property.name;
1849
- }
1850
- if (t3.isStringLiteral(expr.property)) {
1851
- return expr.property.value;
1852
- }
1853
- if (t3.isIdentifier(expr.property)) {
1854
- const binding = path.scope.getBinding(expr.property.name);
1855
- if (!binding?.path.isVariableDeclarator()) return null;
1856
- const init = binding.path.node.init;
1857
- return t3.isStringLiteral(init) ? init.value : null;
1858
- }
1859
- return null;
1860
- }
1861
- function getCallReturnExpression(expr, path) {
1862
- const localReturnExpr = getLocalFunctionReturnExpression(expr, path);
1863
- if (localReturnExpr) return localReturnExpr;
1864
- const firstArg = expr.arguments[0];
1865
- if (firstArg && !t3.isSpreadElement(firstArg) && (t3.isArrowFunctionExpression(firstArg) || t3.isFunctionExpression(firstArg))) {
1866
- return getFunctionLikeReturnExpression(firstArg);
1867
- }
1868
- return null;
1869
- }
1870
- function getLocalFunctionReturnExpression(expr, path) {
1871
- if (!t3.isIdentifier(expr.callee)) return null;
1872
- const binding = path.scope.getBinding(expr.callee.name);
1873
- if (!binding) return null;
1874
- if (binding.path.isFunctionDeclaration()) {
1875
- return getFunctionLikeReturnExpression(binding.path.node);
1876
- }
1877
- if (binding.path.isVariableDeclarator()) {
1878
- const init = binding.path.node.init;
1879
- if (init && (t3.isArrowFunctionExpression(init) || t3.isFunctionExpression(init))) {
1880
- return getFunctionLikeReturnExpression(init);
1881
- }
1882
- }
1883
- return null;
1884
- }
1885
- function getFunctionLikeReturnExpression(fn) {
1886
- if (t3.isExpression(fn.body)) {
1887
- return fn.body;
1888
- }
1889
- if (fn.body.body.length !== 1) return null;
1890
- const stmt = fn.body.body[0];
1891
- return t3.isReturnStatement(stmt) && stmt.argument && t3.isExpression(stmt.argument) ? stmt.argument : null;
1892
- }
1893
1799
 
1894
1800
  // src/plugin/transform.ts
1895
1801
  var traverse2 = _traverse2.default ?? _traverse2;
@@ -1915,7 +1821,7 @@ function transformTruss(code, filename, mapping, options = {}) {
1915
1821
  if (parentPath && parentPath.isMemberExpression() && t4.isIdentifier(parentPath.node.property, { name: "$" })) {
1916
1822
  return;
1917
1823
  }
1918
- const resolvedChain = resolveFullChain(chain, mapping);
1824
+ const resolvedChain = resolveFullChain(chain, mapping, { skipMerge: true });
1919
1825
  sites.push({ path, resolvedChain });
1920
1826
  const line = path.node.loc?.start.line ?? null;
1921
1827
  for (const err of resolvedChain.errors) {
@@ -1925,18 +1831,14 @@ function transformTruss(code, filename, mapping, options = {}) {
1925
1831
  });
1926
1832
  const hasCssPropsCall = hasCssMethodCall(ast, cssBindingName, "props");
1927
1833
  if (sites.length === 0 && !hasCssPropsCall) return null;
1928
- const { createEntries, runtimeLookups, needsMaybeInc } = collectCreateData(sites.map((s) => s.resolvedChain));
1834
+ const chains = sites.map((s) => s.resolvedChain);
1835
+ const { rules, needsMaybeInc } = collectAtomicRules(chains, mapping);
1836
+ const cssText = generateCssText(rules);
1929
1837
  const usedTopLevelNames = collectTopLevelBindings(ast);
1930
- const existingStylexNamespace = findStylexNamespaceImport(ast);
1931
- const stylexNamespaceName = existingStylexNamespace ?? reservePreferredName(usedTopLevelNames, "stylex");
1932
- const createVarName = reservePreferredName(usedTopLevelNames, "css", "css_");
1933
1838
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
1934
1839
  const existingMergePropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "mergeProps");
1935
1840
  const mergePropsHelperName = existingMergePropsHelperName ?? reservePreferredName(usedTopLevelNames, "mergeProps");
1936
1841
  const needsMergePropsHelper = { current: false };
1937
- const existingAsStyleArrayHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "asStyleArray");
1938
- const asStyleArrayHelperName = existingAsStyleArrayHelperName ?? reservePreferredName(usedTopLevelNames, "asStyleArray");
1939
- const needsAsStyleArrayHelper = { current: false };
1940
1842
  const existingTrussPropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "trussProps");
1941
1843
  const trussPropsHelperName = existingTrussPropsHelperName ?? reservePreferredName(usedTopLevelNames, "trussProps");
1942
1844
  const needsTrussPropsHelper = { current: false };
@@ -1944,18 +1846,17 @@ function transformTruss(code, filename, mapping, options = {}) {
1944
1846
  const trussDebugInfoName = existingTrussDebugInfoName ?? reservePreferredName(usedTopLevelNames, "TrussDebugInfo");
1945
1847
  const needsTrussDebugInfo = { current: false };
1946
1848
  const runtimeLookupNames = /* @__PURE__ */ new Map();
1849
+ const runtimeLookups = collectRuntimeLookups(chains);
1947
1850
  for (const [lookupKey] of runtimeLookups) {
1948
1851
  runtimeLookupNames.set(lookupKey, reservePreferredName(usedTopLevelNames, `__${lookupKey}`));
1949
1852
  }
1950
- const createProperties = buildCreateProperties(createEntries, stylexNamespaceName);
1951
1853
  rewriteExpressionSites({
1952
1854
  ast,
1953
1855
  sites,
1954
1856
  cssBindingName,
1955
1857
  filename: basename(filename),
1956
1858
  debug: options.debug ?? false,
1957
- createVarName,
1958
- stylexNamespaceName,
1859
+ mapping,
1959
1860
  maybeIncHelperName,
1960
1861
  mergePropsHelperName,
1961
1862
  needsMergePropsHelper,
@@ -1963,55 +1864,50 @@ function transformTruss(code, filename, mapping, options = {}) {
1963
1864
  needsTrussPropsHelper,
1964
1865
  trussDebugInfoName,
1965
1866
  needsTrussDebugInfo,
1966
- asStyleArrayHelperName,
1967
- needsAsStyleArrayHelper,
1968
1867
  skippedCssPropMessages: errorMessages,
1969
1868
  runtimeLookupNames
1970
1869
  });
1971
1870
  removeCssImport(ast, cssBindingName);
1972
- if (!findStylexNamespaceImport(ast)) {
1973
- insertStylexNamespaceImport(ast, stylexNamespaceName);
1871
+ const runtimeImports = [];
1872
+ if (needsTrussPropsHelper.current) {
1873
+ runtimeImports.push({ importedName: "trussProps", localName: trussPropsHelperName });
1974
1874
  }
1975
- if (needsMergePropsHelper.current || needsAsStyleArrayHelper.current || needsTrussPropsHelper.current || needsTrussDebugInfo.current) {
1976
- const runtimeImports = [];
1977
- if (needsMergePropsHelper.current) {
1978
- runtimeImports.push({ importedName: "mergeProps", localName: mergePropsHelperName });
1979
- }
1980
- if (needsAsStyleArrayHelper.current) {
1981
- runtimeImports.push({ importedName: "asStyleArray", localName: asStyleArrayHelperName });
1982
- }
1983
- if (needsTrussPropsHelper.current) {
1984
- runtimeImports.push({ importedName: "trussProps", localName: trussPropsHelperName });
1985
- }
1986
- if (needsTrussDebugInfo.current) {
1987
- runtimeImports.push({ importedName: "TrussDebugInfo", localName: trussDebugInfoName });
1988
- }
1875
+ if (needsMergePropsHelper.current) {
1876
+ runtimeImports.push({ importedName: "mergeProps", localName: mergePropsHelperName });
1877
+ }
1878
+ if (needsTrussDebugInfo.current) {
1879
+ runtimeImports.push({ importedName: "TrussDebugInfo", localName: trussDebugInfoName });
1880
+ }
1881
+ if (options.injectCss) {
1882
+ runtimeImports.push({ importedName: "__injectTrussCSS", localName: "__injectTrussCSS" });
1883
+ }
1884
+ if (runtimeImports.length > 0) {
1989
1885
  upsertNamedImports(ast, "@homebound/truss/runtime", runtimeImports);
1990
1886
  }
1991
- const markerVarNames = collectReferencedMarkerNames(createEntries);
1992
- const hoistedMarkerDecls = hoistMarkerDeclarations(ast, markerVarNames);
1993
1887
  const declarationsToInsert = [];
1994
1888
  if (maybeIncHelperName) {
1995
1889
  declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
1996
1890
  }
1997
- declarationsToInsert.push(...hoistedMarkerDecls);
1998
- if (createProperties.length > 0) {
1999
- declarationsToInsert.push(buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties));
2000
- for (const [lookupKey, lookup] of runtimeLookups) {
2001
- const lookupName = runtimeLookupNames.get(lookupKey);
2002
- if (!lookupName) continue;
2003
- declarationsToInsert.push(buildRuntimeLookupDeclaration(lookupName, createVarName, lookup));
2004
- }
1891
+ for (const [lookupKey, lookup] of runtimeLookups) {
1892
+ const lookupName = runtimeLookupNames.get(lookupKey);
1893
+ if (!lookupName) continue;
1894
+ declarationsToInsert.push(buildRuntimeLookupDeclaration(lookupName, lookup.segmentsByName, mapping));
1895
+ }
1896
+ if (options.injectCss && cssText.length > 0) {
1897
+ declarationsToInsert.push(
1898
+ t4.expressionStatement(t4.callExpression(t4.identifier("__injectTrussCSS"), [t4.stringLiteral(cssText)]))
1899
+ );
2005
1900
  }
2006
1901
  for (const { message, line } of errorMessages) {
2007
1902
  const location = line !== null ? `${filename}:${line}` : filename;
2008
1903
  const logMessage = `${message} (${location})`;
2009
- const consoleError = t4.expressionStatement(
2010
- t4.callExpression(t4.memberExpression(t4.identifier("console"), t4.identifier("error")), [
2011
- t4.stringLiteral(logMessage)
2012
- ])
1904
+ declarationsToInsert.push(
1905
+ t4.expressionStatement(
1906
+ t4.callExpression(t4.memberExpression(t4.identifier("console"), t4.identifier("error")), [
1907
+ t4.stringLiteral(logMessage)
1908
+ ])
1909
+ )
2013
1910
  );
2014
- declarationsToInsert.push(consoleError);
2015
1911
  }
2016
1912
  if (declarationsToInsert.length > 0) {
2017
1913
  const insertIndex = ast.program.body.findIndex(function(node) {
@@ -2023,46 +1919,23 @@ function transformTruss(code, filename, mapping, options = {}) {
2023
1919
  sourceFileName: filename,
2024
1920
  retainLines: false
2025
1921
  });
2026
- return { code: output.code, map: output.map };
2027
- }
2028
- function collectReferencedMarkerNames(createEntries) {
2029
- const names = /* @__PURE__ */ new Set();
2030
- for (const [, entry] of createEntries) {
2031
- if (entry.whenPseudo?.markerNode && entry.whenPseudo.markerNode.type === "Identifier") {
2032
- names.add(entry.whenPseudo.markerNode.name);
2033
- }
2034
- }
2035
- return names;
1922
+ return { code: output.code, map: output.map, css: cssText, rules };
2036
1923
  }
2037
- function hoistMarkerDeclarations(ast, names) {
2038
- if (names.size === 0) return [];
2039
- const hoisted = [];
2040
- const remaining = new Set(names);
2041
- for (let i = ast.program.body.length - 1; i >= 0; i--) {
2042
- if (remaining.size === 0) break;
2043
- const node = ast.program.body[i];
2044
- if (!t4.isVariableDeclaration(node)) continue;
2045
- const matchingDeclarators = [];
2046
- const otherDeclarators = [];
2047
- for (const decl of node.declarations) {
2048
- if (t4.isIdentifier(decl.id) && remaining.has(decl.id.name)) {
2049
- matchingDeclarators.push(decl);
2050
- remaining.delete(decl.id.name);
2051
- } else {
2052
- otherDeclarators.push(decl);
1924
+ function collectRuntimeLookups(chains) {
1925
+ const lookups = /* @__PURE__ */ new Map();
1926
+ for (const chain of chains) {
1927
+ for (const part of chain.parts) {
1928
+ const segs = part.type === "unconditional" ? part.segments : [...part.thenSegments, ...part.elseSegments];
1929
+ for (const seg of segs) {
1930
+ if (seg.typographyLookup && !lookups.has(seg.typographyLookup.lookupKey)) {
1931
+ lookups.set(seg.typographyLookup.lookupKey, {
1932
+ segmentsByName: seg.typographyLookup.segmentsByName
1933
+ });
1934
+ }
2053
1935
  }
2054
1936
  }
2055
- if (matchingDeclarators.length === 0) continue;
2056
- if (otherDeclarators.length === 0) {
2057
- ast.program.body.splice(i, 1);
2058
- hoisted.push(node);
2059
- } else {
2060
- node.declarations = otherDeclarators;
2061
- hoisted.push(t4.variableDeclaration(node.kind, matchingDeclarators));
2062
- }
2063
1937
  }
2064
- hoisted.reverse();
2065
- return hoisted;
1938
+ return lookups;
2066
1939
  }
2067
1940
 
2068
1941
  // src/plugin/transform-css.ts
@@ -2231,8 +2104,8 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2231
2104
  if (seg.error) {
2232
2105
  return { error: seg.error };
2233
2106
  }
2234
- if (seg.dynamicProps && !seg.argResolved) {
2235
- return { error: `dynamic value with variable argument is not supported in .css.ts files` };
2107
+ if (seg.variableProps && !seg.argResolved) {
2108
+ return { error: `variable value with variable argument is not supported in .css.ts files` };
2236
2109
  }
2237
2110
  if (seg.typographyLookup) {
2238
2111
  return { error: `typography() with a runtime key is not supported in .css.ts files` };
@@ -2254,7 +2127,7 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2254
2127
  }
2255
2128
  for (const [prop, value] of Object.entries(seg.defs)) {
2256
2129
  if (typeof value === "string" || typeof value === "number") {
2257
- declarations.push({ property: camelToKebab(prop), value: String(value) });
2130
+ declarations.push({ property: camelToKebab2(prop), value: String(value) });
2258
2131
  } else {
2259
2132
  return { error: `unexpected nested value for property "${prop}"` };
2260
2133
  }
@@ -2263,7 +2136,7 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2263
2136
  }
2264
2137
  return { declarations };
2265
2138
  }
2266
- function camelToKebab(s) {
2139
+ function camelToKebab2(s) {
2267
2140
  return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
2268
2141
  }
2269
2142
  function formatCssRule(selector, declarations) {
@@ -2331,11 +2204,19 @@ function toVirtualCssSpecifier(source) {
2331
2204
  // src/plugin/index.ts
2332
2205
  var VIRTUAL_CSS_PREFIX = "\0truss-css:";
2333
2206
  var CSS_TS_QUERY = "?truss-css";
2207
+ var VIRTUAL_CSS_ENDPOINT = "/virtual:truss.css";
2208
+ var VIRTUAL_RUNTIME_ID = "virtual:truss:runtime";
2209
+ var RESOLVED_VIRTUAL_RUNTIME_ID = "\0" + VIRTUAL_RUNTIME_ID;
2334
2210
  function trussPlugin(opts) {
2335
2211
  let mapping = null;
2336
2212
  let projectRoot;
2337
2213
  let debug = false;
2214
+ let isTest = false;
2215
+ let isBuild = false;
2338
2216
  const externalPackages = opts.externalPackages ?? [];
2217
+ const cssRegistry = /* @__PURE__ */ new Map();
2218
+ let cssVersion = 0;
2219
+ let lastSentVersion = 0;
2339
2220
  function mappingPath() {
2340
2221
  return resolve(projectRoot || process.cwd(), opts.mapping);
2341
2222
  }
@@ -2345,23 +2226,98 @@ function trussPlugin(opts) {
2345
2226
  }
2346
2227
  return mapping;
2347
2228
  }
2229
+ function collectCss() {
2230
+ return generateCssText(cssRegistry);
2231
+ }
2348
2232
  return {
2349
- name: "truss-stylex",
2233
+ name: "truss",
2350
2234
  enforce: "pre",
2351
2235
  configResolved(config) {
2352
2236
  projectRoot = config.root;
2353
2237
  debug = config.command === "serve" || config.mode === "development" || config.mode === "test";
2238
+ isTest = config.mode === "test";
2239
+ isBuild = config.command === "build";
2354
2240
  },
2355
2241
  buildStart() {
2356
2242
  ensureMapping();
2243
+ cssRegistry.clear();
2244
+ cssVersion = 0;
2245
+ lastSentVersion = 0;
2246
+ },
2247
+ // -- Dev mode HMR --
2248
+ configureServer(server) {
2249
+ if (isTest) return;
2250
+ server.middlewares.use(function(req, res, next) {
2251
+ if (req.url !== VIRTUAL_CSS_ENDPOINT) return next();
2252
+ const css = collectCss();
2253
+ res.setHeader("Content-Type", "text/css");
2254
+ res.setHeader("Cache-Control", "no-store");
2255
+ res.end(css);
2256
+ });
2257
+ const interval = setInterval(function() {
2258
+ if (cssVersion !== lastSentVersion && server.ws) {
2259
+ lastSentVersion = cssVersion;
2260
+ server.ws.send({ type: "custom", event: "truss:css-update" });
2261
+ }
2262
+ }, 150);
2263
+ server.httpServer?.on("close", function() {
2264
+ clearInterval(interval);
2265
+ });
2266
+ },
2267
+ transformIndexHtml(html) {
2268
+ if (isBuild) return html;
2269
+ const tags = [
2270
+ `<link rel="stylesheet" href="${VIRTUAL_CSS_ENDPOINT}">`,
2271
+ `<script type="module" src="/${VIRTUAL_RUNTIME_ID}"></script>`
2272
+ ].join("\n ");
2273
+ return html.replace("</head>", ` ${tags}
2274
+ </head>`);
2275
+ },
2276
+ handleHotUpdate(ctx) {
2277
+ if (ctx.server?.ws) {
2278
+ ctx.server.ws.send({ type: "custom", event: "truss:css-update" });
2279
+ }
2357
2280
  },
2281
+ // -- Virtual module resolution --
2358
2282
  resolveId(source, importer) {
2283
+ if (source === VIRTUAL_RUNTIME_ID || source === "/" + VIRTUAL_RUNTIME_ID) {
2284
+ return RESOLVED_VIRTUAL_RUNTIME_ID;
2285
+ }
2359
2286
  if (!source.endsWith(CSS_TS_QUERY)) return null;
2360
2287
  const absolutePath = resolveImportPath(source.slice(0, -CSS_TS_QUERY.length), importer, projectRoot);
2361
2288
  if (!existsSync(absolutePath)) return null;
2362
2289
  return VIRTUAL_CSS_PREFIX + absolutePath.slice(0, -3);
2363
2290
  },
2364
2291
  load(id) {
2292
+ if (id === RESOLVED_VIRTUAL_RUNTIME_ID) {
2293
+ return `
2294
+ // Truss dev HMR runtime \u2014 keeps styles up to date without page reload
2295
+ (function() {
2296
+ let style = document.getElementById("__truss_virtual__");
2297
+ if (!style) {
2298
+ style = document.createElement("style");
2299
+ style.id = "__truss_virtual__";
2300
+ document.head.appendChild(style);
2301
+ }
2302
+
2303
+ function fetchCss() {
2304
+ fetch("${VIRTUAL_CSS_ENDPOINT}")
2305
+ .then(function(r) { return r.text(); })
2306
+ .then(function(css) { style.textContent = css; })
2307
+ .catch(function() {});
2308
+ }
2309
+
2310
+ fetchCss();
2311
+
2312
+ if (import.meta.hot) {
2313
+ import.meta.hot.on("truss:css-update", fetchCss);
2314
+ import.meta.hot.on("vite:afterUpdate", function() {
2315
+ setTimeout(fetchCss, 50);
2316
+ });
2317
+ }
2318
+ })();
2319
+ `;
2320
+ }
2365
2321
  if (!id.startsWith(VIRTUAL_CSS_PREFIX)) return null;
2366
2322
  const sourcePath = id.slice(VIRTUAL_CSS_PREFIX.length) + ".ts";
2367
2323
  const sourceCode = readFileSync(sourcePath, "utf8");
@@ -2383,12 +2339,62 @@ function trussPlugin(opts) {
2383
2339
  if (!hasCssDsl) {
2384
2340
  return { code: rewrittenCode, map: null };
2385
2341
  }
2386
- const result = transformTruss(rewrittenCode, fileId, ensureMapping(), { debug });
2342
+ const result = transformTruss(rewrittenCode, fileId, ensureMapping(), {
2343
+ debug,
2344
+ // In test mode (jsdom), inject CSS directly so document.styleSheets has rules
2345
+ injectCss: isTest
2346
+ });
2387
2347
  if (!result) {
2388
2348
  if (!rewrittenImports.changed) return null;
2389
2349
  return { code: rewrittenCode, map: null };
2390
2350
  }
2351
+ if (result.rules) {
2352
+ let hasNewRules = false;
2353
+ for (const [className, rule] of result.rules) {
2354
+ if (!cssRegistry.has(className)) {
2355
+ cssRegistry.set(className, rule);
2356
+ hasNewRules = true;
2357
+ }
2358
+ }
2359
+ if (hasNewRules) {
2360
+ cssVersion++;
2361
+ }
2362
+ }
2391
2363
  return { code: result.code, map: result.map };
2364
+ },
2365
+ // -- Production CSS emission --
2366
+ generateBundle(_options, bundle) {
2367
+ if (!isBuild) return;
2368
+ const css = collectCss();
2369
+ if (!css) return;
2370
+ for (const key of Object.keys(bundle)) {
2371
+ const asset = bundle[key];
2372
+ if (asset.type === "asset" && key.endsWith(".css")) {
2373
+ asset.source = asset.source + "\n" + css;
2374
+ return;
2375
+ }
2376
+ }
2377
+ this.emitFile({
2378
+ type: "asset",
2379
+ fileName: "truss.css",
2380
+ source: css
2381
+ });
2382
+ },
2383
+ writeBundle(options, bundle) {
2384
+ if (!isBuild) return;
2385
+ const css = collectCss();
2386
+ if (!css) return;
2387
+ const outDir = options.dir || join(projectRoot, "dist");
2388
+ const trussPath = join(outDir, "truss.css");
2389
+ if (!existsSync(trussPath)) {
2390
+ const alreadyEmitted = Object.keys(bundle).some(function(key) {
2391
+ const asset = bundle[key];
2392
+ return asset.type === "asset" && key.endsWith(".css") && typeof asset.source === "string" && asset.source.includes(css);
2393
+ });
2394
+ if (!alreadyEmitted) {
2395
+ writeFileSync(trussPath, css, "utf8");
2396
+ }
2397
+ }
2392
2398
  }
2393
2399
  };
2394
2400
  }