@homebound/truss 2.0.13 → 2.1.0-next.2

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,571 @@
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
+ const declaration = { cssProperty: camelToKebab(prop), cssValue: `var(${varName})`, cssVarName: varName };
169
+ const existingRule = rules.get(className);
170
+ if (!existingRule) {
171
+ rules.set(className, {
172
+ className,
173
+ cssProperty: declaration.cssProperty,
174
+ cssValue: declaration.cssValue,
175
+ declarations: [declaration],
176
+ pseudoClass: seg.pseudoClass ?? void 0,
177
+ mediaQuery: seg.mediaQuery ?? void 0,
178
+ pseudoElement: seg.pseudoElement ?? void 0,
179
+ cssVarName: varName
180
+ });
181
+ continue;
182
+ }
183
+ existingRule.declarations ??= [
184
+ {
185
+ cssProperty: existingRule.cssProperty,
186
+ cssValue: existingRule.cssValue,
187
+ cssVarName: existingRule.cssVarName
188
+ }
189
+ ];
190
+ if (!existingRule.declarations.some(function(entry) {
191
+ return entry.cssProperty === declaration.cssProperty;
192
+ })) {
193
+ existingRule.declarations.push(declaration);
194
+ }
195
+ }
196
+ if (seg.variableExtraDefs) {
197
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
198
+ const extraBase = `${baseKey}_${cssProp}`;
199
+ const extraName = prefix ? `${prefix}${extraBase}` : extraBase;
200
+ if (!rules.has(extraName)) {
201
+ rules.set(extraName, {
202
+ className: extraName,
203
+ cssProperty: camelToKebab(cssProp),
204
+ cssValue: String(value),
205
+ pseudoClass: seg.pseudoClass ?? void 0,
206
+ mediaQuery: seg.mediaQuery ?? void 0,
207
+ pseudoElement: seg.pseudoElement ?? void 0
208
+ });
209
+ }
210
+ }
211
+ }
212
+ }
213
+ function collectWhenStaticRules(rules, seg, mapping) {
214
+ const wp = seg.whenPseudo;
215
+ const prefix = whenPrefix(wp);
216
+ const rawDefs = seg.defs;
217
+ const isMultiProp = Object.keys(rawDefs).length > 1;
218
+ const mClass = markerClassName(wp.markerNode);
219
+ for (const [cssProp, value] of Object.entries(rawDefs)) {
220
+ const cssValue = typeof value === "string" || typeof value === "number" ? value : extractLeafValue(value);
221
+ if (cssValue === null) continue;
222
+ const baseName = computeStaticBaseName(seg, cssProp, String(cssValue), isMultiProp, mapping);
223
+ const className = `${prefix}${baseName}`;
224
+ if (!rules.has(className)) {
225
+ rules.set(className, {
226
+ className,
227
+ cssProperty: camelToKebab(cssProp),
228
+ cssValue: String(cssValue),
229
+ whenSelector: {
230
+ relationship: wp.relationship ?? "ancestor",
231
+ markerClass: mClass,
232
+ pseudo: wp.pseudo
233
+ }
234
+ });
235
+ }
236
+ }
237
+ }
238
+ function collectWhenVariableRules(rules, seg, mapping) {
239
+ const wp = seg.whenPseudo;
240
+ const prefix = whenPrefix(wp);
241
+ const baseKey = seg.key.split("__")[0];
242
+ const mClass = markerClassName(wp.markerNode);
243
+ for (const prop of seg.variableProps) {
244
+ const className = `${prefix}${baseKey}_var`;
245
+ const varName = toCssVariableName(className, baseKey, prop);
246
+ const declaration = { cssProperty: camelToKebab(prop), cssValue: `var(${varName})`, cssVarName: varName };
247
+ const existingRule = rules.get(className);
248
+ if (!existingRule) {
249
+ rules.set(className, {
250
+ className,
251
+ cssProperty: declaration.cssProperty,
252
+ cssValue: declaration.cssValue,
253
+ declarations: [declaration],
254
+ cssVarName: varName,
255
+ whenSelector: {
256
+ relationship: wp.relationship ?? "ancestor",
257
+ markerClass: mClass,
258
+ pseudo: wp.pseudo
259
+ }
260
+ });
261
+ continue;
262
+ }
263
+ existingRule.declarations ??= [
264
+ {
265
+ cssProperty: existingRule.cssProperty,
266
+ cssValue: existingRule.cssValue,
267
+ cssVarName: existingRule.cssVarName
268
+ }
269
+ ];
270
+ if (!existingRule.declarations.some(function(entry) {
271
+ return entry.cssProperty === declaration.cssProperty;
272
+ })) {
273
+ existingRule.declarations.push(declaration);
274
+ }
275
+ }
276
+ if (seg.variableExtraDefs) {
277
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
278
+ const extraName = `${prefix}${baseKey}_${cssProp}`;
279
+ if (!rules.has(extraName)) {
280
+ rules.set(extraName, {
281
+ className: extraName,
282
+ cssProperty: camelToKebab(cssProp),
283
+ cssValue: String(value),
284
+ whenSelector: {
285
+ relationship: wp.relationship ?? "ancestor",
286
+ markerClass: mClass,
287
+ pseudo: wp.pseudo
288
+ }
289
+ });
290
+ }
291
+ }
292
+ }
293
+ }
294
+ function unwrapDefs(defs, pseudoElement) {
295
+ let result = defs;
296
+ if (pseudoElement && result[pseudoElement] && typeof result[pseudoElement] === "object") {
297
+ result = result[pseudoElement];
298
+ }
299
+ const unwrapped = {};
300
+ for (const [prop, val] of Object.entries(result)) {
301
+ unwrapped[prop] = extractLeafValue(val) ?? val;
302
+ }
303
+ return unwrapped;
304
+ }
305
+ function extractLeafValue(value) {
306
+ if (typeof value === "string" || typeof value === "number") return value;
307
+ if (value === null) return null;
308
+ if (typeof value === "object") {
309
+ const obj = value;
310
+ for (const [k, v] of Object.entries(obj)) {
311
+ if (k === "default") continue;
312
+ if (typeof v === "string" || typeof v === "number") return v;
313
+ if (typeof v === "object" && v !== null) return extractLeafValue(v);
314
+ }
315
+ if ("default" in obj && obj.default !== null) {
316
+ return extractLeafValue(obj.default);
317
+ }
318
+ }
319
+ return null;
320
+ }
321
+ function generateCssText(rules) {
322
+ const allRules = Array.from(rules.values());
323
+ const base = [];
324
+ const pseudo = /* @__PURE__ */ new Map();
325
+ const pseudoElement = [];
326
+ const whenRules = [];
327
+ const media = [];
328
+ const mediaPseudo = [];
329
+ const mediaPseudoElement = [];
330
+ for (const rule of allRules) {
331
+ if (rule.whenSelector) {
332
+ whenRules.push(rule);
333
+ } else if (rule.mediaQuery && rule.pseudoClass) {
334
+ mediaPseudo.push(rule);
335
+ } else if (rule.mediaQuery && rule.pseudoElement) {
336
+ mediaPseudoElement.push(rule);
337
+ } else if (rule.mediaQuery) {
338
+ media.push(rule);
339
+ } else if (rule.pseudoClass && rule.pseudoElement) {
340
+ const tier = pseudo.get(rule.pseudoClass) ?? [];
341
+ tier.push(rule);
342
+ pseudo.set(rule.pseudoClass, tier);
343
+ } else if (rule.pseudoElement) {
344
+ pseudoElement.push(rule);
345
+ } else if (rule.pseudoClass) {
346
+ const tier = pseudo.get(rule.pseudoClass) ?? [];
347
+ tier.push(rule);
348
+ pseudo.set(rule.pseudoClass, tier);
349
+ } else {
350
+ base.push(rule);
351
+ }
352
+ }
353
+ const lines = [];
354
+ for (const rule of base) {
355
+ lines.push(formatBaseRule(rule));
356
+ }
357
+ for (const pc of PSEUDO_ORDER) {
358
+ const tier = pseudo.get(pc);
359
+ if (!tier) continue;
360
+ for (const rule of tier) {
361
+ lines.push(formatPseudoRule(rule));
362
+ }
363
+ }
364
+ for (const [pc, tier] of Array.from(pseudo.entries())) {
365
+ if (PSEUDO_ORDER.includes(pc)) continue;
366
+ for (const rule of tier) {
367
+ lines.push(formatPseudoRule(rule));
368
+ }
369
+ }
370
+ for (const rule of pseudoElement) {
371
+ lines.push(formatPseudoElementRule(rule));
372
+ }
373
+ for (const rule of whenRules) {
374
+ lines.push(formatWhenRule(rule));
375
+ }
376
+ for (const rule of media) {
377
+ lines.push(formatMediaRule(rule));
378
+ }
379
+ for (const rule of mediaPseudo) {
380
+ lines.push(formatMediaPseudoRule(rule));
381
+ }
382
+ for (const rule of mediaPseudoElement) {
383
+ lines.push(formatMediaPseudoElementRule(rule));
384
+ }
385
+ for (const rule of allRules) {
386
+ for (const declaration of getRuleDeclarations(rule)) {
387
+ if (declaration.cssVarName) {
388
+ lines.push(`@property ${declaration.cssVarName} {
389
+ syntax: "*";
390
+ inherits: false;
391
+ }`);
392
+ }
393
+ }
394
+ }
395
+ return lines.join("\n");
396
+ }
397
+ function formatBaseRule(rule) {
398
+ return formatRuleBlock(`.${rule.className}`, rule);
399
+ }
400
+ function formatPseudoRule(rule) {
401
+ const pe = rule.pseudoElement ? rule.pseudoElement : "";
402
+ return formatRuleBlock(`.${rule.className}${rule.pseudoClass}${pe}`, rule);
403
+ }
404
+ function formatPseudoElementRule(rule) {
405
+ return formatRuleBlock(`.${rule.className}${rule.pseudoElement}`, rule);
406
+ }
407
+ function formatWhenRule(rule) {
408
+ const whenSelector = rule.whenSelector;
409
+ if (!whenSelector) {
410
+ return formatBaseRule(rule);
411
+ }
412
+ const markerSelector = `.${whenSelector.markerClass}${whenSelector.pseudo}`;
413
+ const targetSelector = `.${rule.className}`;
414
+ if (whenSelector.relationship === "ancestor") {
415
+ return formatRuleBlock(`${markerSelector} ${targetSelector}`, rule);
416
+ }
417
+ if (whenSelector.relationship === "descendant") {
418
+ return formatRuleBlock(`${targetSelector}:has(${markerSelector})`, rule);
419
+ }
420
+ if (whenSelector.relationship === "siblingAfter") {
421
+ return formatRuleBlock(`${targetSelector}:has(~ ${markerSelector})`, rule);
422
+ }
423
+ if (whenSelector.relationship === "siblingBefore") {
424
+ return formatRuleBlock(`${markerSelector} ~ ${targetSelector}`, rule);
425
+ }
426
+ if (whenSelector.relationship === "anySibling") {
427
+ return formatRuleBlock(`${targetSelector}:has(~ ${markerSelector}), ${markerSelector} ~ ${targetSelector}`, rule);
428
+ }
429
+ return formatRuleBlock(`${markerSelector} ${targetSelector}`, rule);
430
+ }
431
+ function formatMediaRule(rule) {
432
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}`, rule);
433
+ }
434
+ function formatMediaPseudoRule(rule) {
435
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}${rule.pseudoClass}`, rule);
436
+ }
437
+ function formatMediaPseudoElementRule(rule) {
438
+ const pe = rule.pseudoElement ?? "";
439
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}${pe}`, rule);
440
+ }
441
+ function getRuleDeclarations(rule) {
442
+ return rule.declarations ?? [{ cssProperty: rule.cssProperty, cssValue: rule.cssValue, cssVarName: rule.cssVarName }];
443
+ }
444
+ function formatRuleBlock(selector, rule) {
445
+ const body = getRuleDeclarations(rule).map(function(declaration) {
446
+ return ` ${declaration.cssProperty}: ${declaration.cssValue};`;
447
+ }).join("\n");
448
+ return `${selector} {
449
+ ${body}
450
+ }`;
451
+ }
452
+ function formatNestedRuleBlock(wrapper, selector, rule) {
453
+ const body = getRuleDeclarations(rule).map(function(declaration) {
454
+ return ` ${declaration.cssProperty}: ${declaration.cssValue};`;
455
+ }).join("\n");
456
+ return `${wrapper} {
457
+ ${selector} {
458
+ ${body}
459
+ }
460
+ }`;
461
+ }
462
+ function buildStyleHashProperties(segments, mapping, maybeIncHelperName) {
463
+ const propGroups = /* @__PURE__ */ new Map();
464
+ for (const seg of segments) {
465
+ if (seg.error || seg.styleArrayArg || seg.typographyLookup) continue;
466
+ if (seg.variableProps) {
467
+ const prefix = seg.whenPseudo ? whenPrefix(seg.whenPseudo) : conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
468
+ const baseKey = seg.key.split("__")[0];
469
+ for (const prop of seg.variableProps) {
470
+ const className = prefix ? `${prefix}${baseKey}_var` : `${baseKey}_var`;
471
+ const varName = toCssVariableName(className, baseKey, prop);
472
+ if (!propGroups.has(prop)) propGroups.set(prop, []);
473
+ propGroups.get(prop).push({
474
+ className,
475
+ isVariable: true,
476
+ varName,
477
+ argNode: seg.argNode,
478
+ incremented: seg.incremented,
479
+ appendPx: seg.appendPx
480
+ });
481
+ }
482
+ if (seg.variableExtraDefs) {
483
+ for (const [cssProp, value] of Object.entries(seg.variableExtraDefs)) {
484
+ const extraBase = `${baseKey}_${cssProp}`;
485
+ const extraName = prefix ? `${prefix}${extraBase}` : extraBase;
486
+ if (!propGroups.has(cssProp)) propGroups.set(cssProp, []);
487
+ propGroups.get(cssProp).push({ className: extraName, isVariable: false });
488
+ }
489
+ }
490
+ } else {
491
+ const rawDefs = seg.whenPseudo ? seg.defs : unwrapDefs(seg.defs, seg.pseudoElement);
492
+ const prefix = seg.whenPseudo ? whenPrefix(seg.whenPseudo) : conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints);
493
+ const isMultiProp = Object.keys(rawDefs).length > 1;
494
+ for (const cssProp of Object.keys(rawDefs)) {
495
+ const val = extractLeafValue(rawDefs[cssProp]);
496
+ if (val === null) continue;
497
+ const baseName = computeStaticBaseName(seg, cssProp, String(val), isMultiProp, mapping);
498
+ const className = prefix ? `${prefix}${baseName}` : baseName;
499
+ if (!propGroups.has(cssProp)) propGroups.set(cssProp, []);
500
+ propGroups.get(cssProp).push({ className, isVariable: false });
501
+ }
502
+ }
503
+ }
504
+ const properties = [];
505
+ for (const [cssProp, entries] of Array.from(propGroups.entries())) {
506
+ const classNames = entries.map((e) => e.className).join(" ");
507
+ const variableEntries = entries.filter((e) => e.isVariable);
508
+ if (variableEntries.length > 0) {
509
+ const varsProps = [];
510
+ for (const dyn of variableEntries) {
511
+ let valueExpr = dyn.argNode;
512
+ if (dyn.incremented) {
513
+ valueExpr = t.callExpression(t.identifier(maybeIncHelperName ?? "__maybeInc"), [valueExpr]);
514
+ } else if (dyn.appendPx) {
515
+ valueExpr = t.templateLiteral(
516
+ [t.templateElement({ raw: "", cooked: "" }, false), t.templateElement({ raw: "px", cooked: "px" }, true)],
517
+ [valueExpr]
518
+ );
519
+ }
520
+ varsProps.push(t.objectProperty(t.stringLiteral(dyn.varName), valueExpr));
521
+ }
522
+ const tuple = t.arrayExpression([t.stringLiteral(classNames), t.objectExpression(varsProps)]);
523
+ properties.push(t.objectProperty(toPropertyKey(cssProp), tuple));
524
+ } else {
525
+ properties.push(t.objectProperty(toPropertyKey(cssProp), t.stringLiteral(classNames)));
526
+ }
527
+ }
528
+ return properties;
529
+ }
530
+ function toCssVariableName(className, baseKey, cssProp) {
531
+ const baseClassName = `${baseKey}_var`;
532
+ const conditionPrefix2 = className.endsWith(baseClassName) ? className.slice(0, -baseClassName.length) : "";
533
+ return `--${conditionPrefix2}${cssProp}`;
534
+ }
535
+ function buildMaybeIncDeclaration(helperName, increment) {
536
+ const incParam = t.identifier("inc");
537
+ const body = t.blockStatement([
538
+ t.returnStatement(
539
+ t.conditionalExpression(
540
+ t.binaryExpression("===", t.unaryExpression("typeof", incParam), t.stringLiteral("string")),
541
+ incParam,
542
+ t.templateLiteral(
543
+ [t.templateElement({ raw: "", cooked: "" }, false), t.templateElement({ raw: "px", cooked: "px" }, true)],
544
+ [t.binaryExpression("*", incParam, t.numericLiteral(increment))]
545
+ )
546
+ )
547
+ )
548
+ ]);
549
+ return t.variableDeclaration("const", [
550
+ t.variableDeclarator(t.identifier(helperName), t.arrowFunctionExpression([incParam], body))
551
+ ]);
552
+ }
553
+ function toPropertyKey(key) {
554
+ return isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(key);
555
+ }
556
+ function isValidIdentifier(s) {
557
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
558
+ }
559
+ function buildRuntimeLookupDeclaration(lookupName, segmentsByName, mapping) {
560
+ const properties = [];
561
+ for (const [name, segs] of Object.entries(segmentsByName)) {
562
+ const hashProps = buildStyleHashProperties(segs, mapping);
563
+ properties.push(t.objectProperty(t.identifier(name), t.objectExpression(hashProps)));
564
+ }
565
+ return t.variableDeclaration("const", [
566
+ t.variableDeclarator(t.identifier(lookupName), t.objectExpression(properties))
567
+ ]);
568
+ }
4
569
 
5
570
  // src/plugin/transform.ts
6
571
  import { parse } from "@babel/parser";
@@ -10,7 +575,7 @@ import * as t4 from "@babel/types";
10
575
  import { basename } from "path";
11
576
 
12
577
  // src/plugin/resolve-chain.ts
13
- function resolveFullChain(chain, mapping) {
578
+ function resolveFullChain(chain, mapping, options) {
14
579
  const parts = [];
15
580
  const markers = [];
16
581
  const filteredChain = [];
@@ -33,6 +598,31 @@ function resolveFullChain(chain, mapping) {
33
598
  let currentNodes = [];
34
599
  while (i < filteredChain.length) {
35
600
  const node = filteredChain[i];
601
+ const mediaStart = getMediaConditionalStartNode(node, mapping);
602
+ if (mediaStart) {
603
+ const elseIndex = findElseIndex(filteredChain, i + 1);
604
+ if (elseIndex !== -1) {
605
+ if (currentNodes.length > 0) {
606
+ const unconditionalSegs = resolveChain(currentNodes, mapping);
607
+ parts.push({
608
+ type: "unconditional",
609
+ segments: options?.skipMerge ? unconditionalSegs : mergeOverlappingConditions(unconditionalSegs)
610
+ });
611
+ currentNodes = [];
612
+ }
613
+ const thenNodes = mediaStart.thenNodes ? [...mediaStart.thenNodes, ...filteredChain.slice(i + 1, elseIndex)] : filteredChain.slice(i, elseIndex);
614
+ const elseNodes = [makeMediaQueryNode(mediaStart.inverseMediaQuery), ...filteredChain.slice(elseIndex + 1)];
615
+ const thenSegs = resolveChain(thenNodes, mapping);
616
+ const elseSegs = resolveChain(elseNodes, mapping);
617
+ const combinedSegs = [...thenSegs, ...elseSegs];
618
+ parts.push({
619
+ type: "unconditional",
620
+ segments: options?.skipMerge ? combinedSegs : mergeOverlappingConditions(combinedSegs)
621
+ });
622
+ i = filteredChain.length;
623
+ break;
624
+ }
625
+ }
36
626
  if (node.type === "if") {
37
627
  if (node.conditionNode.type === "StringLiteral") {
38
628
  const mediaQuery = node.conditionNode.value;
@@ -41,9 +631,10 @@ function resolveFullChain(chain, mapping) {
41
631
  continue;
42
632
  }
43
633
  if (currentNodes.length > 0) {
634
+ const unconditionalSegs = resolveChain(currentNodes, mapping);
44
635
  parts.push({
45
636
  type: "unconditional",
46
- segments: mergeOverlappingConditions(resolveChain(currentNodes, mapping))
637
+ segments: options?.skipMerge ? unconditionalSegs : mergeOverlappingConditions(unconditionalSegs)
47
638
  });
48
639
  currentNodes = [];
49
640
  }
@@ -67,11 +658,13 @@ function resolveFullChain(chain, mapping) {
67
658
  }
68
659
  i++;
69
660
  }
661
+ const thenSegs = resolveChain(thenNodes, mapping);
662
+ const elseSegs = resolveChain(elseNodes, mapping);
70
663
  parts.push({
71
664
  type: "conditional",
72
665
  conditionNode: node.conditionNode,
73
- thenSegments: mergeOverlappingConditions(resolveChain(thenNodes, mapping)),
74
- elseSegments: mergeOverlappingConditions(resolveChain(elseNodes, mapping))
666
+ thenSegments: options?.skipMerge ? thenSegs : mergeOverlappingConditions(thenSegs),
667
+ elseSegments: options?.skipMerge ? elseSegs : mergeOverlappingConditions(elseSegs)
75
668
  });
76
669
  } else {
77
670
  currentNodes.push(node);
@@ -79,7 +672,11 @@ function resolveFullChain(chain, mapping) {
79
672
  }
80
673
  }
81
674
  if (currentNodes.length > 0) {
82
- parts.push({ type: "unconditional", segments: mergeOverlappingConditions(resolveChain(currentNodes, mapping)) });
675
+ const remainingSegs = resolveChain(currentNodes, mapping);
676
+ parts.push({
677
+ type: "unconditional",
678
+ segments: options?.skipMerge ? remainingSegs : mergeOverlappingConditions(remainingSegs)
679
+ });
83
680
  }
84
681
  const segmentErrors = [];
85
682
  for (const part of parts) {
@@ -92,6 +689,53 @@ function resolveFullChain(chain, mapping) {
92
689
  }
93
690
  return { parts, markers, errors: [...scanErrors, ...segmentErrors] };
94
691
  }
692
+ function getMediaConditionalStartNode(node, mapping) {
693
+ if (node.type === "if" && node.conditionNode.type === "StringLiteral") {
694
+ return {
695
+ inverseMediaQuery: invertMediaQuery(node.conditionNode.value),
696
+ thenNodes: [makeMediaQueryNode(node.conditionNode.value)]
697
+ };
698
+ }
699
+ if (node.type === "getter" && mapping.breakpoints && node.name in mapping.breakpoints) {
700
+ return { inverseMediaQuery: invertMediaQuery(mapping.breakpoints[node.name]) };
701
+ }
702
+ return null;
703
+ }
704
+ function findElseIndex(chain, start) {
705
+ for (let i = start; i < chain.length; i++) {
706
+ if (chain[i].type === "if") {
707
+ return -1;
708
+ }
709
+ if (chain[i].type === "else") {
710
+ return i;
711
+ }
712
+ }
713
+ return -1;
714
+ }
715
+ function makeMediaQueryNode(mediaQuery) {
716
+ return { type: "__mediaQuery", mediaQuery };
717
+ }
718
+ function invertMediaQuery(query) {
719
+ const screenPrefix = "@media screen and ";
720
+ if (query.startsWith(screenPrefix)) {
721
+ const conditions = query.slice(screenPrefix.length).trim();
722
+ const rangeMatch = conditions.match(/^\(min-width: (\d+)px\) and \(max-width: (\d+)px\)$/);
723
+ if (rangeMatch) {
724
+ const min = Number(rangeMatch[1]);
725
+ const max = Number(rangeMatch[2]);
726
+ return `@media screen and (max-width: ${min - 1}px), screen and (min-width: ${max + 1}px)`;
727
+ }
728
+ const minMatch = conditions.match(/^\(min-width: (\d+)px\)$/);
729
+ if (minMatch) {
730
+ return `@media screen and (max-width: ${Number(minMatch[1]) - 1}px)`;
731
+ }
732
+ const maxMatch = conditions.match(/^\(max-width: (\d+)px\)$/);
733
+ if (maxMatch) {
734
+ return `@media screen and (min-width: ${Number(maxMatch[1]) + 1}px)`;
735
+ }
736
+ }
737
+ return query.replace("@media", "@media not");
738
+ }
95
739
  function resolveChain(chain, mapping) {
96
740
  const segments = [];
97
741
  let currentMediaQuery = null;
@@ -139,7 +783,14 @@ function resolveChain(chain, mapping) {
139
783
  continue;
140
784
  }
141
785
  if (abbr === "add") {
142
- const seg = resolveAddCall(node, mapping, currentMediaQuery, currentPseudoClass, currentPseudoElement);
786
+ const seg = resolveAddCall(
787
+ node,
788
+ mapping,
789
+ currentMediaQuery,
790
+ currentPseudoClass,
791
+ currentPseudoElement,
792
+ currentWhenPseudo
793
+ );
143
794
  segments.push(seg);
144
795
  continue;
145
796
  }
@@ -184,15 +835,16 @@ function resolveChain(chain, mapping) {
184
835
  if (!entry) {
185
836
  throw new UnsupportedPatternError(`Unknown abbreviation "${abbr}"`);
186
837
  }
187
- if (entry.kind === "dynamic") {
188
- const seg = resolveDynamicCall(
838
+ if (entry.kind === "variable") {
839
+ const seg = resolveVariableCall(
189
840
  abbr,
190
841
  entry,
191
842
  node,
192
843
  mapping,
193
844
  currentMediaQuery,
194
845
  currentPseudoClass,
195
- currentPseudoElement
846
+ currentPseudoElement,
847
+ currentWhenPseudo
196
848
  );
197
849
  segments.push(seg);
198
850
  } else if (entry.kind === "delegate") {
@@ -203,7 +855,8 @@ function resolveChain(chain, mapping) {
203
855
  mapping,
204
856
  currentMediaQuery,
205
857
  currentPseudoClass,
206
- currentPseudoElement
858
+ currentPseudoElement,
859
+ currentWhenPseudo
207
860
  );
208
861
  segments.push(seg);
209
862
  } else {
@@ -267,7 +920,7 @@ function resolveTypographyEntry(name, mapping, mediaQuery, pseudoClass, pseudoEl
267
920
  }
268
921
  const resolved = resolveEntry(name, entry, mapping, mediaQuery, pseudoClass, pseudoElement, null);
269
922
  for (const segment of resolved) {
270
- if (segment.dynamicProps || segment.whenPseudo) {
923
+ if (segment.variableProps || segment.whenPseudo) {
271
924
  throw new UnsupportedPatternError(`Typography abbreviation "${name}" cannot require runtime arguments`);
272
925
  }
273
926
  }
@@ -311,19 +964,43 @@ function resolveEntry(abbr, entry, mapping, mediaQuery, pseudoClass, pseudoEleme
311
964
  }
312
965
  return result;
313
966
  }
314
- case "dynamic":
967
+ case "variable":
315
968
  case "delegate":
316
969
  throw new UnsupportedPatternError(`Abbreviation "${abbr}" requires arguments \u2014 use ${abbr}() not .${abbr}`);
317
970
  default:
318
971
  throw new UnsupportedPatternError(`Unhandled entry kind for "${abbr}"`);
319
972
  }
320
973
  }
321
- function resolveDynamicCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement) {
974
+ function resolveVariableCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
322
975
  if (node.args.length !== 1) {
323
976
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
324
977
  }
325
978
  const argAst = node.args[0];
326
979
  const literalValue = tryEvaluateLiteral(argAst, entry.incremented, mapping.increment);
980
+ if (whenPseudo) {
981
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
982
+ if (literalValue !== null) {
983
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
984
+ const key = `${abbr}__${keySuffix}__${wpSuffix}`;
985
+ const defs = {};
986
+ for (const prop of entry.props) {
987
+ defs[prop] = literalValue;
988
+ }
989
+ if (entry.extraDefs) Object.assign(defs, entry.extraDefs);
990
+ return { key, defs, whenPseudo, argResolved: literalValue };
991
+ } else {
992
+ const key = `${abbr}__${wpSuffix}`;
993
+ return {
994
+ key,
995
+ defs: {},
996
+ whenPseudo,
997
+ variableProps: entry.props,
998
+ incremented: entry.incremented,
999
+ variableExtraDefs: entry.extraDefs,
1000
+ argNode: argAst
1001
+ };
1002
+ }
1003
+ }
327
1004
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
328
1005
  if (literalValue !== null) {
329
1006
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
@@ -351,23 +1028,48 @@ function resolveDynamicCall(abbr, entry, node, mapping, mediaQuery, pseudoClass,
351
1028
  defs: {},
352
1029
  mediaQuery,
353
1030
  pseudoClass,
354
- dynamicProps: entry.props,
1031
+ variableProps: entry.props,
355
1032
  incremented: entry.incremented,
356
- dynamicExtraDefs: entry.extraDefs,
1033
+ variableExtraDefs: entry.extraDefs,
357
1034
  argNode: argAst
358
1035
  };
359
1036
  }
360
1037
  }
361
- function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement) {
1038
+ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
362
1039
  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`);
1040
+ if (!targetEntry || targetEntry.kind !== "variable") {
1041
+ throw new UnsupportedPatternError(`Delegate "${abbr}" targets "${entry.target}" which is not a variable entry`);
365
1042
  }
366
1043
  if (node.args.length !== 1) {
367
1044
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
368
1045
  }
369
1046
  const argAst = node.args[0];
370
1047
  const literalValue = tryEvaluatePxLiteral(argAst);
1048
+ if (whenPseudo) {
1049
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
1050
+ if (literalValue !== null) {
1051
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
1052
+ const key = `${entry.target}__${keySuffix}__${wpSuffix}`;
1053
+ const defs = {};
1054
+ for (const prop of targetEntry.props) {
1055
+ defs[prop] = literalValue;
1056
+ }
1057
+ if (targetEntry.extraDefs) Object.assign(defs, targetEntry.extraDefs);
1058
+ return { key, defs, whenPseudo, argResolved: literalValue };
1059
+ } else {
1060
+ const key = `${entry.target}__${wpSuffix}`;
1061
+ return {
1062
+ key,
1063
+ defs: {},
1064
+ whenPseudo,
1065
+ variableProps: targetEntry.props,
1066
+ incremented: false,
1067
+ appendPx: true,
1068
+ variableExtraDefs: targetEntry.extraDefs,
1069
+ argNode: argAst
1070
+ };
1071
+ }
1072
+ }
371
1073
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
372
1074
  if (literalValue !== null) {
373
1075
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_");
@@ -396,15 +1098,15 @@ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
396
1098
  mediaQuery,
397
1099
  pseudoClass,
398
1100
  pseudoElement,
399
- dynamicProps: targetEntry.props,
1101
+ variableProps: targetEntry.props,
400
1102
  incremented: false,
401
1103
  appendPx: true,
402
- dynamicExtraDefs: targetEntry.extraDefs,
1104
+ variableExtraDefs: targetEntry.extraDefs,
403
1105
  argNode: argAst
404
1106
  };
405
1107
  }
406
1108
  }
407
- function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
1109
+ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
408
1110
  if (node.args.length === 1) {
409
1111
  const styleArg = node.args[0];
410
1112
  if (styleArg.type === "SpreadElement") {
@@ -433,6 +1135,17 @@ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
433
1135
  const propName = propArg.value;
434
1136
  const valueArg = node.args[1];
435
1137
  const literalValue = tryEvaluateAddLiteral(valueArg);
1138
+ if (whenPseudo) {
1139
+ const wpSuffix = whenPseudoKeyName(whenPseudo);
1140
+ if (literalValue !== null) {
1141
+ const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1142
+ const key = `add_${propName}__${keySuffix}__${wpSuffix}`;
1143
+ return { key, defs: { [propName]: literalValue }, whenPseudo, argResolved: literalValue };
1144
+ } else {
1145
+ const key = `add_${propName}__${wpSuffix}`;
1146
+ return { key, defs: {}, whenPseudo, variableProps: [propName], incremented: false, argNode: valueArg };
1147
+ }
1148
+ }
436
1149
  const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
437
1150
  if (literalValue !== null) {
438
1151
  const keySuffix = literalValue.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
@@ -455,7 +1168,7 @@ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
455
1168
  mediaQuery,
456
1169
  pseudoClass,
457
1170
  pseudoElement,
458
- dynamicProps: [propName],
1171
+ variableProps: [propName],
459
1172
  incremented: false,
460
1173
  argNode: valueArg
461
1174
  };
@@ -530,7 +1243,7 @@ function mergeOverlappingConditions(segments) {
530
1243
  const propToIndices = /* @__PURE__ */ new Map();
531
1244
  for (let i = 0; i < segments.length; i++) {
532
1245
  const seg = segments[i];
533
- if (seg.dynamicProps || seg.styleArrayArg || seg.whenPseudo || seg.error) continue;
1246
+ if (seg.variableProps || seg.styleArrayArg || seg.whenPseudo || seg.error) continue;
534
1247
  for (const prop of Object.keys(seg.defs)) {
535
1248
  if (!propToIndices.has(prop)) propToIndices.set(prop, []);
536
1249
  propToIndices.get(prop).push(i);
@@ -746,44 +1459,44 @@ var UnsupportedPatternError = class extends Error {
746
1459
  };
747
1460
 
748
1461
  // src/plugin/ast-utils.ts
749
- import * as t from "@babel/types";
1462
+ import * as t2 from "@babel/types";
750
1463
  function collectTopLevelBindings(ast) {
751
1464
  const used = /* @__PURE__ */ new Set();
752
1465
  for (const node of ast.program.body) {
753
- if (t.isImportDeclaration(node)) {
1466
+ if (t2.isImportDeclaration(node)) {
754
1467
  for (const spec of node.specifiers) {
755
1468
  used.add(spec.local.name);
756
1469
  }
757
1470
  continue;
758
1471
  }
759
- if (t.isVariableDeclaration(node)) {
1472
+ if (t2.isVariableDeclaration(node)) {
760
1473
  for (const decl of node.declarations) {
761
1474
  collectPatternBindings(decl.id, used);
762
1475
  }
763
1476
  continue;
764
1477
  }
765
- if (t.isFunctionDeclaration(node) && node.id) {
1478
+ if (t2.isFunctionDeclaration(node) && node.id) {
766
1479
  used.add(node.id.name);
767
1480
  continue;
768
1481
  }
769
- if (t.isClassDeclaration(node) && node.id) {
1482
+ if (t2.isClassDeclaration(node) && node.id) {
770
1483
  used.add(node.id.name);
771
1484
  continue;
772
1485
  }
773
- if (t.isExportNamedDeclaration(node) && node.declaration) {
1486
+ if (t2.isExportNamedDeclaration(node) && node.declaration) {
774
1487
  const decl = node.declaration;
775
- if (t.isVariableDeclaration(decl)) {
1488
+ if (t2.isVariableDeclaration(decl)) {
776
1489
  for (const varDecl of decl.declarations) {
777
1490
  collectPatternBindings(varDecl.id, used);
778
1491
  }
779
- } else if ((t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) && decl.id) {
1492
+ } else if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
780
1493
  used.add(decl.id.name);
781
1494
  }
782
1495
  continue;
783
1496
  }
784
- if (t.isExportDefaultDeclaration(node)) {
1497
+ if (t2.isExportDefaultDeclaration(node)) {
785
1498
  const decl = node.declaration;
786
- if ((t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) && decl.id) {
1499
+ if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
787
1500
  used.add(decl.id.name);
788
1501
  }
789
1502
  }
@@ -791,37 +1504,37 @@ function collectTopLevelBindings(ast) {
791
1504
  return used;
792
1505
  }
793
1506
  function collectPatternBindings(pattern, used) {
794
- if (t.isVoidPattern(pattern)) {
1507
+ if (t2.isVoidPattern(pattern)) {
795
1508
  return;
796
1509
  }
797
- if (t.isIdentifier(pattern)) {
1510
+ if (t2.isIdentifier(pattern)) {
798
1511
  used.add(pattern.name);
799
1512
  return;
800
1513
  }
801
- if (t.isAssignmentPattern(pattern)) {
1514
+ if (t2.isAssignmentPattern(pattern)) {
802
1515
  collectPatternBindings(pattern.left, used);
803
1516
  return;
804
1517
  }
805
- if (t.isRestElement(pattern)) {
1518
+ if (t2.isRestElement(pattern)) {
806
1519
  collectPatternBindings(pattern.argument, used);
807
1520
  return;
808
1521
  }
809
- if (t.isObjectPattern(pattern)) {
1522
+ if (t2.isObjectPattern(pattern)) {
810
1523
  for (const prop of pattern.properties) {
811
- if (t.isObjectProperty(prop)) {
1524
+ if (t2.isObjectProperty(prop)) {
812
1525
  collectPatternBindings(prop.value, used);
813
- } else if (t.isRestElement(prop)) {
1526
+ } else if (t2.isRestElement(prop)) {
814
1527
  collectPatternBindings(prop.argument, used);
815
1528
  }
816
1529
  }
817
1530
  return;
818
1531
  }
819
- if (t.isArrayPattern(pattern)) {
1532
+ if (t2.isArrayPattern(pattern)) {
820
1533
  for (const el of pattern.elements) {
821
1534
  if (!el) continue;
822
- if (t.isIdentifier(el) || t.isAssignmentPattern(el) || t.isObjectPattern(el) || t.isArrayPattern(el)) {
1535
+ if (t2.isIdentifier(el) || t2.isAssignmentPattern(el) || t2.isObjectPattern(el) || t2.isArrayPattern(el)) {
823
1536
  collectPatternBindings(el, used);
824
- } else if (t.isRestElement(el)) {
1537
+ } else if (t2.isRestElement(el)) {
825
1538
  collectPatternBindings(el.argument, used);
826
1539
  }
827
1540
  }
@@ -848,9 +1561,9 @@ function reservePreferredName(used, preferred, secondary) {
848
1561
  }
849
1562
  function findCssImportBinding(ast) {
850
1563
  for (const node of ast.program.body) {
851
- if (!t.isImportDeclaration(node)) continue;
1564
+ if (!t2.isImportDeclaration(node)) continue;
852
1565
  for (const spec of node.specifiers) {
853
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: "Css" })) {
1566
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: "Css" })) {
854
1567
  return spec.local.name;
855
1568
  }
856
1569
  }
@@ -859,9 +1572,9 @@ function findCssImportBinding(ast) {
859
1572
  }
860
1573
  function hasCssMethodCall(ast, binding, method) {
861
1574
  let found = false;
862
- t.traverseFast(ast, (node) => {
1575
+ t2.traverseFast(ast, (node) => {
863
1576
  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 })) {
1577
+ 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
1578
  found = true;
866
1579
  }
867
1580
  });
@@ -870,8 +1583,8 @@ function hasCssMethodCall(ast, binding, method) {
870
1583
  function removeCssImport(ast, cssBinding) {
871
1584
  for (let i = 0; i < ast.program.body.length; i++) {
872
1585
  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);
1586
+ if (!t2.isImportDeclaration(node)) continue;
1587
+ const cssSpecIndex = node.specifiers.findIndex((s) => t2.isImportSpecifier(s) && s.local.name === cssBinding);
875
1588
  if (cssSpecIndex === -1) continue;
876
1589
  if (node.specifiers.length === 1) {
877
1590
  ast.program.body.splice(i, 1);
@@ -881,40 +1594,20 @@ function removeCssImport(ast, cssBinding) {
881
1594
  return;
882
1595
  }
883
1596
  }
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
1597
  function findLastImportIndex(ast) {
897
1598
  let lastImportIndex = -1;
898
1599
  for (let i = 0; i < ast.program.body.length; i++) {
899
- if (t.isImportDeclaration(ast.program.body[i])) {
1600
+ if (t2.isImportDeclaration(ast.program.body[i])) {
900
1601
  lastImportIndex = i;
901
1602
  }
902
1603
  }
903
1604
  return lastImportIndex;
904
1605
  }
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
1606
  function findNamedImportBinding(ast, source, importedName) {
914
1607
  for (const node of ast.program.body) {
915
- if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
1608
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
916
1609
  for (const spec of node.specifiers) {
917
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: importedName })) {
1610
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: importedName })) {
918
1611
  return spec.local.name;
919
1612
  }
920
1613
  }
@@ -924,21 +1617,21 @@ function findNamedImportBinding(ast, source, importedName) {
924
1617
  function upsertNamedImports(ast, source, imports) {
925
1618
  if (imports.length === 0) return;
926
1619
  for (const node of ast.program.body) {
927
- if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
1620
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
928
1621
  for (const entry of imports) {
929
1622
  const exists = node.specifiers.some(function(spec) {
930
- return t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: entry.importedName });
1623
+ return t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: entry.importedName });
931
1624
  });
932
1625
  if (exists) continue;
933
- node.specifiers.push(t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName)));
1626
+ node.specifiers.push(t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName)));
934
1627
  }
935
1628
  return;
936
1629
  }
937
- const importDecl = t.importDeclaration(
1630
+ const importDecl = t2.importDeclaration(
938
1631
  imports.map(function(entry) {
939
- return t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName));
1632
+ return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
940
1633
  }),
941
- t.stringLiteral(source)
1634
+ t2.stringLiteral(source)
942
1635
  );
943
1636
  const idx = findLastImportIndex(ast);
944
1637
  ast.program.body.splice(idx + 1, 0, importDecl);
@@ -947,11 +1640,11 @@ function extractChain(node, cssBinding) {
947
1640
  const chain = [];
948
1641
  let current = node;
949
1642
  while (true) {
950
- if (t.isIdentifier(current, { name: cssBinding })) {
1643
+ if (t2.isIdentifier(current, { name: cssBinding })) {
951
1644
  chain.reverse();
952
1645
  return chain;
953
1646
  }
954
- if (t.isMemberExpression(current) && !current.computed && t.isIdentifier(current.property)) {
1647
+ if (t2.isMemberExpression(current) && !current.computed && t2.isIdentifier(current.property)) {
955
1648
  const name = current.property.name;
956
1649
  if (name === "else") {
957
1650
  chain.push({ type: "else" });
@@ -961,7 +1654,7 @@ function extractChain(node, cssBinding) {
961
1654
  current = current.object;
962
1655
  continue;
963
1656
  }
964
- if (t.isCallExpression(current) && t.isMemberExpression(current.callee) && !current.callee.computed && t.isIdentifier(current.callee.property)) {
1657
+ if (t2.isCallExpression(current) && t2.isMemberExpression(current.callee) && !current.callee.computed && t2.isIdentifier(current.callee.property)) {
965
1658
  const name = current.callee.property.name;
966
1659
  if (name === "if") {
967
1660
  chain.push({
@@ -983,643 +1676,151 @@ function extractChain(node, cssBinding) {
983
1676
  }
984
1677
  }
985
1678
 
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
- }
1679
+ // src/plugin/rewrite-sites.ts
1680
+ import _traverse from "@babel/traverse";
1681
+ import _generate from "@babel/generator";
1682
+ import * as t3 from "@babel/types";
1683
+ var generate = _generate.default ?? _generate;
1684
+ var traverse = _traverse.default ?? _traverse;
1685
+ function rewriteExpressionSites(options) {
1686
+ for (const site of options.sites) {
1687
+ const styleHash = buildStyleHashFromChain(site.resolvedChain, options);
1688
+ const cssAttrPath = getCssAttributePath(site.path);
1689
+ const line = site.path.node.loc?.start.line ?? null;
1690
+ if (cssAttrPath) {
1691
+ cssAttrPath.replaceWith(t3.jsxSpreadAttribute(buildCssSpreadExpression(cssAttrPath, styleHash, line, options)));
1692
+ } else {
1693
+ if (options.debug && line !== null) {
1694
+ injectDebugInfo(styleHash, line, options);
1029
1695
  }
1696
+ site.path.replaceWith(styleHash);
1030
1697
  }
1031
1698
  }
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
- )
1699
+ rewriteCssPropsCalls(options);
1700
+ rewriteCssAttributeExpressions(options);
1701
+ }
1702
+ function getCssAttributePath(path) {
1703
+ const parentPath = path.parentPath;
1704
+ if (!parentPath || !parentPath.isJSXExpressionContainer()) return null;
1705
+ const attrPath = parentPath.parentPath;
1706
+ if (!attrPath || !attrPath.isJSXAttribute()) return null;
1707
+ if (!t3.isJSXIdentifier(attrPath.node.name, { name: "css" })) return null;
1708
+ return attrPath;
1709
+ }
1710
+ function buildStyleHashFromChain(chain, options) {
1711
+ const members = [];
1712
+ if (chain.markers.length > 0) {
1713
+ const markerClasses = chain.markers.map(function(marker) {
1714
+ return markerClassName(marker.markerNode);
1050
1715
  });
1716
+ members.push(t3.objectProperty(t3.identifier("__marker"), t3.stringLiteral(markerClasses.join(" "))));
1051
1717
  }
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
- });
1718
+ for (const part of chain.parts) {
1719
+ if (part.type === "unconditional") {
1720
+ members.push(...buildStyleHashMembers(part.segments, options));
1721
+ } else {
1722
+ const thenMembers = buildStyleHashMembers(part.thenSegments, options);
1723
+ const elseMembers = buildStyleHashMembers(part.elseSegments, options);
1724
+ members.push(
1725
+ t3.spreadElement(
1726
+ t3.conditionalExpression(part.conditionNode, t3.objectExpression(thenMembers), t3.objectExpression(elseMembers))
1727
+ )
1728
+ );
1060
1729
  }
1061
1730
  }
1731
+ return t3.objectExpression(members);
1062
1732
  }
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));
1733
+ function buildStyleHashMembers(segments, options) {
1734
+ const members = [];
1735
+ const normalSegs = [];
1736
+ function flushNormal() {
1737
+ if (normalSegs.length > 0) {
1738
+ members.push(...buildStyleHashProperties(normalSegs, options.mapping, options.maybeIncHelperName));
1739
+ normalSegs.length = 0;
1740
+ }
1741
+ }
1742
+ for (const seg of segments) {
1743
+ if (seg.error) continue;
1744
+ if (seg.styleArrayArg) {
1745
+ flushNormal();
1746
+ members.push(t3.spreadElement(seg.styleArrayArg));
1142
1747
  continue;
1143
1748
  }
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
- )
1749
+ if (seg.typographyLookup) {
1750
+ flushNormal();
1751
+ const lookupName = options.runtimeLookupNames.get(seg.typographyLookup.lookupKey);
1752
+ if (lookupName) {
1753
+ const lookupAccess = t3.memberExpression(
1754
+ t3.identifier(lookupName),
1755
+ seg.typographyLookup.argNode,
1756
+ true
1168
1757
  );
1758
+ members.push(t3.spreadElement(t3.logicalExpression("??", lookupAccess, t3.objectExpression([]))));
1169
1759
  }
1170
- createProperties.push(t2.objectProperty(toPropertyKey(entry.key), t2.objectExpression(props)));
1171
1760
  continue;
1172
1761
  }
1173
- if (entry.defs) {
1174
- createProperties.push(t2.objectProperty(toPropertyKey(entry.key), defsToAst(entry.defs)));
1175
- }
1762
+ normalSegs.push(seg);
1176
1763
  }
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
- ]);
1764
+ flushNormal();
1765
+ return members;
1196
1766
  }
1197
- function buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties) {
1198
- const createCall = t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("create")), [
1199
- t2.objectExpression(createProperties)
1767
+ function injectDebugInfo(expr, line, options) {
1768
+ if (!options.debug) return;
1769
+ const firstProp = expr.properties.find(function(p) {
1770
+ return t3.isObjectProperty(p) && !(t3.isIdentifier(p.key) && p.key.name === "__marker" || t3.isStringLiteral(p.key) && p.key.value === "__marker");
1771
+ });
1772
+ if (!firstProp) return;
1773
+ options.needsTrussDebugInfo.current = true;
1774
+ const debugExpr = t3.newExpression(t3.identifier(options.trussDebugInfoName), [
1775
+ t3.stringLiteral(`${options.filename}:${line}`)
1200
1776
  ]);
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)));
1777
+ if (t3.isStringLiteral(firstProp.value)) {
1778
+ firstProp.value = t3.arrayExpression([firstProp.value, debugExpr]);
1779
+ } else if (t3.isArrayExpression(firstProp.value)) {
1780
+ firstProp.value.elements.push(debugExpr);
1210
1781
  }
1211
- return t2.variableDeclaration("const", [
1212
- t2.variableDeclarator(t2.identifier(lookupName), t2.objectExpression(properties))
1213
- ]);
1214
1782
  }
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
- // src/plugin/rewrite-sites.ts
1246
- import _traverse from "@babel/traverse";
1247
- import _generate from "@babel/generator";
1248
- import * as t3 from "@babel/types";
1249
- var generate = _generate.default ?? _generate;
1250
- 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
- function rewriteExpressionSites(options) {
1259
- for (const site of options.sites) {
1260
- const propsArgs = buildPropsArgsFromChain(site.resolvedChain, options);
1261
- const cssAttrPath = getCssAttributePath(site.path);
1262
- 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;
1276
- }
1277
- site.path.replaceWith(buildStyleArrayExpression(propsArgs, site.path.node.loc?.start.line ?? null, options));
1278
- }
1279
- 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
- );
1308
- }
1309
- function getCssAttributePath(path) {
1310
- const parentPath = path.parentPath;
1311
- if (!parentPath || !parentPath.isJSXExpressionContainer()) return null;
1312
- const attrPath = parentPath.parentPath;
1313
- if (!attrPath || !attrPath.isJSXAttribute()) return null;
1314
- if (!t3.isJSXIdentifier(attrPath.node.name, { name: "css" })) return null;
1315
- return attrPath;
1316
- }
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
- }
1330
- }
1331
- for (const part of chain.parts) {
1332
- 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(
1342
- t3.spreadElement(
1343
- t3.conditionalExpression(part.conditionNode, t3.arrayExpression(thenArgs), t3.arrayExpression(elseArgs))
1344
- )
1345
- );
1346
- }
1347
- }
1348
- return args;
1349
- }
1350
- function buildPropsArgs(segments, options) {
1351
- const args = [];
1352
- for (const seg of segments) {
1353
- 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
- if (seg.styleArrayArg) {
1368
- args.push(
1369
- t3.spreadElement(
1370
- buildUnknownSpreadFallback(
1371
- seg.styleArrayArg,
1372
- options.asStyleArrayHelperName,
1373
- options.needsAsStyleArrayHelper
1374
- )
1375
- )
1376
- );
1377
- continue;
1378
- }
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")
1391
- );
1392
- } else {
1393
- argExpr = t3.callExpression(t3.identifier("String"), [seg.argNode]);
1394
- }
1395
- args.push(t3.callExpression(ref, [argExpr]));
1396
- } else {
1397
- args.push(ref);
1398
- }
1783
+ function buildCssSpreadExpression(path, styleHash, line, options) {
1784
+ const existingClassNameExpr = removeExistingAttribute(path, "className");
1785
+ const existingStyleExpr = removeExistingAttribute(path, "style");
1786
+ if (!existingClassNameExpr && !existingStyleExpr) {
1787
+ return buildPropsCall(styleHash, line, options);
1399
1788
  }
1400
- return args;
1401
- }
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
- }
1443
- });
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
- );
1789
+ options.needsMergePropsHelper.current = true;
1790
+ if (options.debug && line !== null) {
1791
+ injectDebugInfo(styleHash, line, options);
1456
1792
  }
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
1793
+ return t3.callExpression(t3.identifier(options.mergePropsHelperName), [
1794
+ existingClassNameExpr ?? t3.identifier("undefined"),
1795
+ existingStyleExpr ?? t3.identifier("undefined"),
1796
+ styleHash
1463
1797
  ]);
1464
1798
  }
1465
- function buildDebugElements(line, options) {
1466
- if (!options.debug || line === null) {
1467
- return [];
1468
- }
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
- }
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)})`;
1799
+ function buildPropsCall(styleHash, line, options) {
1800
+ options.needsTrussPropsHelper.current = true;
1801
+ if (options.debug && line !== null && t3.isObjectExpression(styleHash)) {
1802
+ injectDebugInfo(styleHash, line, options);
1487
1803
  }
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
1503
- ]);
1804
+ return t3.callExpression(t3.identifier(options.trussPropsHelperName), [styleHash]);
1504
1805
  }
1505
- function removeExistingClassNameAttribute(path) {
1806
+ function removeExistingAttribute(path, attrName) {
1506
1807
  const openingElement = path.parentPath;
1507
1808
  if (!openingElement || !openingElement.isJSXOpeningElement()) return null;
1508
1809
  const attrs = openingElement.node.attributes;
1509
1810
  for (let i = 0; i < attrs.length; i++) {
1510
1811
  const attr = attrs[i];
1511
- if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: "className" })) continue;
1512
- let classNameExpr = null;
1812
+ if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: attrName })) continue;
1813
+ let expr = null;
1513
1814
  if (t3.isStringLiteral(attr.value)) {
1514
- classNameExpr = attr.value;
1815
+ expr = attr.value;
1515
1816
  } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1516
- classNameExpr = attr.value.expression;
1817
+ expr = attr.value.expression;
1517
1818
  }
1518
1819
  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))];
1820
+ return expr;
1573
1821
  }
1574
1822
  return null;
1575
1823
  }
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
1824
  function rewriteCssPropsCalls(options) {
1624
1825
  traverse(options.ast, {
1625
1826
  CallExpression(path) {
@@ -1627,28 +1828,51 @@ function rewriteCssPropsCalls(options) {
1627
1828
  const arg = path.node.arguments[0];
1628
1829
  if (!arg || t3.isSpreadElement(arg) || !t3.isExpression(arg) || path.node.arguments.length !== 1) return;
1629
1830
  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
1831
+ options.needsTrussPropsHelper.current = true;
1832
+ const classNameExpr = extractSiblingClassName(path);
1833
+ if (classNameExpr) {
1834
+ options.needsMergePropsHelper.current = true;
1835
+ path.replaceWith(
1836
+ t3.callExpression(t3.identifier(options.mergePropsHelperName), [classNameExpr, t3.identifier("undefined"), arg])
1638
1837
  );
1639
- propsArgs = result.elements.filter((e) => e !== null);
1640
1838
  } else {
1641
- propsArgs = [t3.spreadElement(arg)];
1839
+ path.replaceWith(t3.callExpression(t3.identifier(options.trussPropsHelperName), [arg]));
1642
1840
  }
1643
- const classNameExpr = extractSiblingClassName(path);
1644
- if (classNameExpr) {
1645
- path.replaceWith(buildMergePropsCall(propsArgs, classNameExpr, line, options));
1841
+ }
1842
+ });
1843
+ }
1844
+ function rewriteCssAttributeExpressions(options) {
1845
+ traverse(options.ast, {
1846
+ JSXAttribute(path) {
1847
+ if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1848
+ const value = path.node.value;
1849
+ if (!t3.isJSXExpressionContainer(value)) return;
1850
+ if (!t3.isExpression(value.expression)) return;
1851
+ const expr = value.expression;
1852
+ const line = path.node.loc?.start.line ?? null;
1853
+ const existingClassNameExpr = removeExistingAttribute(path, "className");
1854
+ const existingStyleExpr = removeExistingAttribute(path, "style");
1855
+ if (existingClassNameExpr || existingStyleExpr) {
1856
+ options.needsMergePropsHelper.current = true;
1857
+ path.replaceWith(
1858
+ t3.jsxSpreadAttribute(
1859
+ t3.callExpression(t3.identifier(options.mergePropsHelperName), [
1860
+ existingClassNameExpr ?? t3.identifier("undefined"),
1861
+ existingStyleExpr ?? t3.identifier("undefined"),
1862
+ expr
1863
+ ])
1864
+ )
1865
+ );
1646
1866
  } else {
1647
- path.replaceWith(buildPropsCall(propsArgs, line, options));
1867
+ options.needsTrussPropsHelper.current = true;
1868
+ path.replaceWith(t3.jsxSpreadAttribute(t3.callExpression(t3.identifier(options.trussPropsHelperName), [expr])));
1648
1869
  }
1649
1870
  }
1650
1871
  });
1651
1872
  }
1873
+ function isCssPropsCall(expr, cssBindingName) {
1874
+ return t3.isMemberExpression(expr.callee) && !expr.callee.computed && t3.isIdentifier(expr.callee.object, { name: cssBindingName }) && t3.isIdentifier(expr.callee.property, { name: "props" });
1875
+ }
1652
1876
  function extractSiblingClassName(callPath) {
1653
1877
  const spreadPath = callPath.parentPath;
1654
1878
  if (!spreadPath || !spreadPath.isSpreadElement()) return null;
@@ -1666,230 +1890,9 @@ function extractSiblingClassName(callPath) {
1666
1890
  }
1667
1891
  return null;
1668
1892
  }
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
1893
  function isMatchingPropertyName(key, name) {
1820
1894
  return t3.isIdentifier(key) && key.name === name || t3.isStringLiteral(key) && key.value === name;
1821
1895
  }
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
1896
 
1894
1897
  // src/plugin/transform.ts
1895
1898
  var traverse2 = _traverse2.default ?? _traverse2;
@@ -1915,7 +1918,7 @@ function transformTruss(code, filename, mapping, options = {}) {
1915
1918
  if (parentPath && parentPath.isMemberExpression() && t4.isIdentifier(parentPath.node.property, { name: "$" })) {
1916
1919
  return;
1917
1920
  }
1918
- const resolvedChain = resolveFullChain(chain, mapping);
1921
+ const resolvedChain = resolveFullChain(chain, mapping, { skipMerge: true });
1919
1922
  sites.push({ path, resolvedChain });
1920
1923
  const line = path.node.loc?.start.line ?? null;
1921
1924
  for (const err of resolvedChain.errors) {
@@ -1925,18 +1928,14 @@ function transformTruss(code, filename, mapping, options = {}) {
1925
1928
  });
1926
1929
  const hasCssPropsCall = hasCssMethodCall(ast, cssBindingName, "props");
1927
1930
  if (sites.length === 0 && !hasCssPropsCall) return null;
1928
- const { createEntries, runtimeLookups, needsMaybeInc } = collectCreateData(sites.map((s) => s.resolvedChain));
1931
+ const chains = sites.map((s) => s.resolvedChain);
1932
+ const { rules, needsMaybeInc } = collectAtomicRules(chains, mapping);
1933
+ const cssText = generateCssText(rules);
1929
1934
  const usedTopLevelNames = collectTopLevelBindings(ast);
1930
- const existingStylexNamespace = findStylexNamespaceImport(ast);
1931
- const stylexNamespaceName = existingStylexNamespace ?? reservePreferredName(usedTopLevelNames, "stylex");
1932
- const createVarName = reservePreferredName(usedTopLevelNames, "css", "css_");
1933
1935
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
1934
1936
  const existingMergePropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "mergeProps");
1935
1937
  const mergePropsHelperName = existingMergePropsHelperName ?? reservePreferredName(usedTopLevelNames, "mergeProps");
1936
1938
  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
1939
  const existingTrussPropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "trussProps");
1941
1940
  const trussPropsHelperName = existingTrussPropsHelperName ?? reservePreferredName(usedTopLevelNames, "trussProps");
1942
1941
  const needsTrussPropsHelper = { current: false };
@@ -1944,18 +1943,17 @@ function transformTruss(code, filename, mapping, options = {}) {
1944
1943
  const trussDebugInfoName = existingTrussDebugInfoName ?? reservePreferredName(usedTopLevelNames, "TrussDebugInfo");
1945
1944
  const needsTrussDebugInfo = { current: false };
1946
1945
  const runtimeLookupNames = /* @__PURE__ */ new Map();
1946
+ const runtimeLookups = collectRuntimeLookups(chains);
1947
1947
  for (const [lookupKey] of runtimeLookups) {
1948
1948
  runtimeLookupNames.set(lookupKey, reservePreferredName(usedTopLevelNames, `__${lookupKey}`));
1949
1949
  }
1950
- const createProperties = buildCreateProperties(createEntries, stylexNamespaceName);
1951
1950
  rewriteExpressionSites({
1952
1951
  ast,
1953
1952
  sites,
1954
1953
  cssBindingName,
1955
1954
  filename: basename(filename),
1956
1955
  debug: options.debug ?? false,
1957
- createVarName,
1958
- stylexNamespaceName,
1956
+ mapping,
1959
1957
  maybeIncHelperName,
1960
1958
  mergePropsHelperName,
1961
1959
  needsMergePropsHelper,
@@ -1963,55 +1961,50 @@ function transformTruss(code, filename, mapping, options = {}) {
1963
1961
  needsTrussPropsHelper,
1964
1962
  trussDebugInfoName,
1965
1963
  needsTrussDebugInfo,
1966
- asStyleArrayHelperName,
1967
- needsAsStyleArrayHelper,
1968
1964
  skippedCssPropMessages: errorMessages,
1969
1965
  runtimeLookupNames
1970
1966
  });
1971
1967
  removeCssImport(ast, cssBindingName);
1972
- if (!findStylexNamespaceImport(ast)) {
1973
- insertStylexNamespaceImport(ast, stylexNamespaceName);
1968
+ const runtimeImports = [];
1969
+ if (needsTrussPropsHelper.current) {
1970
+ runtimeImports.push({ importedName: "trussProps", localName: trussPropsHelperName });
1974
1971
  }
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
- }
1972
+ if (needsMergePropsHelper.current) {
1973
+ runtimeImports.push({ importedName: "mergeProps", localName: mergePropsHelperName });
1974
+ }
1975
+ if (needsTrussDebugInfo.current) {
1976
+ runtimeImports.push({ importedName: "TrussDebugInfo", localName: trussDebugInfoName });
1977
+ }
1978
+ if (options.injectCss) {
1979
+ runtimeImports.push({ importedName: "__injectTrussCSS", localName: "__injectTrussCSS" });
1980
+ }
1981
+ if (runtimeImports.length > 0) {
1989
1982
  upsertNamedImports(ast, "@homebound/truss/runtime", runtimeImports);
1990
1983
  }
1991
- const markerVarNames = collectReferencedMarkerNames(createEntries);
1992
- const hoistedMarkerDecls = hoistMarkerDeclarations(ast, markerVarNames);
1993
1984
  const declarationsToInsert = [];
1994
1985
  if (maybeIncHelperName) {
1995
1986
  declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
1996
1987
  }
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
- }
1988
+ for (const [lookupKey, lookup] of runtimeLookups) {
1989
+ const lookupName = runtimeLookupNames.get(lookupKey);
1990
+ if (!lookupName) continue;
1991
+ declarationsToInsert.push(buildRuntimeLookupDeclaration(lookupName, lookup.segmentsByName, mapping));
1992
+ }
1993
+ if (options.injectCss && cssText.length > 0) {
1994
+ declarationsToInsert.push(
1995
+ t4.expressionStatement(t4.callExpression(t4.identifier("__injectTrussCSS"), [t4.stringLiteral(cssText)]))
1996
+ );
2005
1997
  }
2006
1998
  for (const { message, line } of errorMessages) {
2007
1999
  const location = line !== null ? `${filename}:${line}` : filename;
2008
2000
  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
- ])
2001
+ declarationsToInsert.push(
2002
+ t4.expressionStatement(
2003
+ t4.callExpression(t4.memberExpression(t4.identifier("console"), t4.identifier("error")), [
2004
+ t4.stringLiteral(logMessage)
2005
+ ])
2006
+ )
2013
2007
  );
2014
- declarationsToInsert.push(consoleError);
2015
2008
  }
2016
2009
  if (declarationsToInsert.length > 0) {
2017
2010
  const insertIndex = ast.program.body.findIndex(function(node) {
@@ -2023,46 +2016,23 @@ function transformTruss(code, filename, mapping, options = {}) {
2023
2016
  sourceFileName: filename,
2024
2017
  retainLines: false
2025
2018
  });
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;
2019
+ return { code: output.code, map: output.map, css: cssText, rules };
2036
2020
  }
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);
2021
+ function collectRuntimeLookups(chains) {
2022
+ const lookups = /* @__PURE__ */ new Map();
2023
+ for (const chain of chains) {
2024
+ for (const part of chain.parts) {
2025
+ const segs = part.type === "unconditional" ? part.segments : [...part.thenSegments, ...part.elseSegments];
2026
+ for (const seg of segs) {
2027
+ if (seg.typographyLookup && !lookups.has(seg.typographyLookup.lookupKey)) {
2028
+ lookups.set(seg.typographyLookup.lookupKey, {
2029
+ segmentsByName: seg.typographyLookup.segmentsByName
2030
+ });
2031
+ }
2053
2032
  }
2054
2033
  }
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
2034
  }
2064
- hoisted.reverse();
2065
- return hoisted;
2035
+ return lookups;
2066
2036
  }
2067
2037
 
2068
2038
  // src/plugin/transform-css.ts
@@ -2231,8 +2201,8 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2231
2201
  if (seg.error) {
2232
2202
  return { error: seg.error };
2233
2203
  }
2234
- if (seg.dynamicProps && !seg.argResolved) {
2235
- return { error: `dynamic value with variable argument is not supported in .css.ts files` };
2204
+ if (seg.variableProps && !seg.argResolved) {
2205
+ return { error: `variable value with variable argument is not supported in .css.ts files` };
2236
2206
  }
2237
2207
  if (seg.typographyLookup) {
2238
2208
  return { error: `typography() with a runtime key is not supported in .css.ts files` };
@@ -2254,7 +2224,7 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2254
2224
  }
2255
2225
  for (const [prop, value] of Object.entries(seg.defs)) {
2256
2226
  if (typeof value === "string" || typeof value === "number") {
2257
- declarations.push({ property: camelToKebab(prop), value: String(value) });
2227
+ declarations.push({ property: camelToKebab2(prop), value: String(value) });
2258
2228
  } else {
2259
2229
  return { error: `unexpected nested value for property "${prop}"` };
2260
2230
  }
@@ -2263,7 +2233,7 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
2263
2233
  }
2264
2234
  return { declarations };
2265
2235
  }
2266
- function camelToKebab(s) {
2236
+ function camelToKebab2(s) {
2267
2237
  return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
2268
2238
  }
2269
2239
  function formatCssRule(selector, declarations) {
@@ -2331,11 +2301,19 @@ function toVirtualCssSpecifier(source) {
2331
2301
  // src/plugin/index.ts
2332
2302
  var VIRTUAL_CSS_PREFIX = "\0truss-css:";
2333
2303
  var CSS_TS_QUERY = "?truss-css";
2304
+ var VIRTUAL_CSS_ENDPOINT = "/virtual:truss.css";
2305
+ var VIRTUAL_RUNTIME_ID = "virtual:truss:runtime";
2306
+ var RESOLVED_VIRTUAL_RUNTIME_ID = "\0" + VIRTUAL_RUNTIME_ID;
2334
2307
  function trussPlugin(opts) {
2335
2308
  let mapping = null;
2336
2309
  let projectRoot;
2337
2310
  let debug = false;
2311
+ let isTest = false;
2312
+ let isBuild = false;
2338
2313
  const externalPackages = opts.externalPackages ?? [];
2314
+ const cssRegistry = /* @__PURE__ */ new Map();
2315
+ let cssVersion = 0;
2316
+ let lastSentVersion = 0;
2339
2317
  function mappingPath() {
2340
2318
  return resolve(projectRoot || process.cwd(), opts.mapping);
2341
2319
  }
@@ -2345,23 +2323,98 @@ function trussPlugin(opts) {
2345
2323
  }
2346
2324
  return mapping;
2347
2325
  }
2326
+ function collectCss() {
2327
+ return generateCssText(cssRegistry);
2328
+ }
2348
2329
  return {
2349
- name: "truss-stylex",
2330
+ name: "truss",
2350
2331
  enforce: "pre",
2351
2332
  configResolved(config) {
2352
2333
  projectRoot = config.root;
2353
2334
  debug = config.command === "serve" || config.mode === "development" || config.mode === "test";
2335
+ isTest = config.mode === "test";
2336
+ isBuild = config.command === "build";
2354
2337
  },
2355
2338
  buildStart() {
2356
2339
  ensureMapping();
2340
+ cssRegistry.clear();
2341
+ cssVersion = 0;
2342
+ lastSentVersion = 0;
2357
2343
  },
2344
+ // -- Dev mode HMR --
2345
+ configureServer(server) {
2346
+ if (isTest) return;
2347
+ server.middlewares.use(function(req, res, next) {
2348
+ if (req.url !== VIRTUAL_CSS_ENDPOINT) return next();
2349
+ const css = collectCss();
2350
+ res.setHeader("Content-Type", "text/css");
2351
+ res.setHeader("Cache-Control", "no-store");
2352
+ res.end(css);
2353
+ });
2354
+ const interval = setInterval(function() {
2355
+ if (cssVersion !== lastSentVersion && server.ws) {
2356
+ lastSentVersion = cssVersion;
2357
+ server.ws.send({ type: "custom", event: "truss:css-update" });
2358
+ }
2359
+ }, 150);
2360
+ server.httpServer?.on("close", function() {
2361
+ clearInterval(interval);
2362
+ });
2363
+ },
2364
+ transformIndexHtml(html) {
2365
+ if (isBuild) return html;
2366
+ const tags = [
2367
+ `<link rel="stylesheet" href="${VIRTUAL_CSS_ENDPOINT}">`,
2368
+ `<script type="module" src="/${VIRTUAL_RUNTIME_ID}"></script>`
2369
+ ].join("\n ");
2370
+ return html.replace("</head>", ` ${tags}
2371
+ </head>`);
2372
+ },
2373
+ handleHotUpdate(ctx) {
2374
+ if (ctx.server?.ws) {
2375
+ ctx.server.ws.send({ type: "custom", event: "truss:css-update" });
2376
+ }
2377
+ },
2378
+ // -- Virtual module resolution --
2358
2379
  resolveId(source, importer) {
2380
+ if (source === VIRTUAL_RUNTIME_ID || source === "/" + VIRTUAL_RUNTIME_ID) {
2381
+ return RESOLVED_VIRTUAL_RUNTIME_ID;
2382
+ }
2359
2383
  if (!source.endsWith(CSS_TS_QUERY)) return null;
2360
2384
  const absolutePath = resolveImportPath(source.slice(0, -CSS_TS_QUERY.length), importer, projectRoot);
2361
2385
  if (!existsSync(absolutePath)) return null;
2362
2386
  return VIRTUAL_CSS_PREFIX + absolutePath.slice(0, -3);
2363
2387
  },
2364
2388
  load(id) {
2389
+ if (id === RESOLVED_VIRTUAL_RUNTIME_ID) {
2390
+ return `
2391
+ // Truss dev HMR runtime \u2014 keeps styles up to date without page reload
2392
+ (function() {
2393
+ let style = document.getElementById("__truss_virtual__");
2394
+ if (!style) {
2395
+ style = document.createElement("style");
2396
+ style.id = "__truss_virtual__";
2397
+ document.head.appendChild(style);
2398
+ }
2399
+
2400
+ function fetchCss() {
2401
+ fetch("${VIRTUAL_CSS_ENDPOINT}")
2402
+ .then(function(r) { return r.text(); })
2403
+ .then(function(css) { style.textContent = css; })
2404
+ .catch(function() {});
2405
+ }
2406
+
2407
+ fetchCss();
2408
+
2409
+ if (import.meta.hot) {
2410
+ import.meta.hot.on("truss:css-update", fetchCss);
2411
+ import.meta.hot.on("vite:afterUpdate", function() {
2412
+ setTimeout(fetchCss, 50);
2413
+ });
2414
+ }
2415
+ })();
2416
+ `;
2417
+ }
2365
2418
  if (!id.startsWith(VIRTUAL_CSS_PREFIX)) return null;
2366
2419
  const sourcePath = id.slice(VIRTUAL_CSS_PREFIX.length) + ".ts";
2367
2420
  const sourceCode = readFileSync(sourcePath, "utf8");
@@ -2383,12 +2436,62 @@ function trussPlugin(opts) {
2383
2436
  if (!hasCssDsl) {
2384
2437
  return { code: rewrittenCode, map: null };
2385
2438
  }
2386
- const result = transformTruss(rewrittenCode, fileId, ensureMapping(), { debug });
2439
+ const result = transformTruss(rewrittenCode, fileId, ensureMapping(), {
2440
+ debug,
2441
+ // In test mode (jsdom), inject CSS directly so document.styleSheets has rules
2442
+ injectCss: isTest
2443
+ });
2387
2444
  if (!result) {
2388
2445
  if (!rewrittenImports.changed) return null;
2389
2446
  return { code: rewrittenCode, map: null };
2390
2447
  }
2448
+ if (result.rules) {
2449
+ let hasNewRules = false;
2450
+ for (const [className, rule] of result.rules) {
2451
+ if (!cssRegistry.has(className)) {
2452
+ cssRegistry.set(className, rule);
2453
+ hasNewRules = true;
2454
+ }
2455
+ }
2456
+ if (hasNewRules) {
2457
+ cssVersion++;
2458
+ }
2459
+ }
2391
2460
  return { code: result.code, map: result.map };
2461
+ },
2462
+ // -- Production CSS emission --
2463
+ generateBundle(_options, bundle) {
2464
+ if (!isBuild) return;
2465
+ const css = collectCss();
2466
+ if (!css) return;
2467
+ for (const key of Object.keys(bundle)) {
2468
+ const asset = bundle[key];
2469
+ if (asset.type === "asset" && key.endsWith(".css")) {
2470
+ asset.source = asset.source + "\n" + css;
2471
+ return;
2472
+ }
2473
+ }
2474
+ this.emitFile({
2475
+ type: "asset",
2476
+ fileName: "truss.css",
2477
+ source: css
2478
+ });
2479
+ },
2480
+ writeBundle(options, bundle) {
2481
+ if (!isBuild) return;
2482
+ const css = collectCss();
2483
+ if (!css) return;
2484
+ const outDir = options.dir || join(projectRoot, "dist");
2485
+ const trussPath = join(outDir, "truss.css");
2486
+ if (!existsSync(trussPath)) {
2487
+ const alreadyEmitted = Object.keys(bundle).some(function(key) {
2488
+ const asset = bundle[key];
2489
+ return asset.type === "asset" && key.endsWith(".css") && typeof asset.source === "string" && asset.source.includes(css);
2490
+ });
2491
+ if (!alreadyEmitted) {
2492
+ writeFileSync(trussPath, css, "utf8");
2493
+ }
2494
+ }
2392
2495
  }
2393
2496
  };
2394
2497
  }