@homebound/truss 2.29.6 → 2.29.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,6 +56,7 @@ function removeCssImport(ast, cssBinding) {
56
56
  ast.program.body.splice(i, 1);
57
57
  } else {
58
58
  node.specifiers.splice(cssSpecIndex, 1);
59
+ hoistTypeOnlyImportKind(node);
59
60
  }
60
61
  return;
61
62
  }
@@ -76,10 +77,10 @@ function insertAfterLeadingImports(ast, statements) {
76
77
  }
77
78
  function findNamedImportBinding(ast, importedName, source) {
78
79
  for (const node of ast.program.body) {
79
- if (!t.isImportDeclaration(node)) continue;
80
+ if (!t.isImportDeclaration(node) || node.importKind === "type") continue;
80
81
  if (source !== void 0 && node.source.value !== source) continue;
81
82
  for (const spec of node.specifiers) {
82
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: importedName })) {
83
+ if (t.isImportSpecifier(spec) && spec.importKind !== "type" && t.isIdentifier(spec.imported, { name: importedName })) {
83
84
  return spec.local.name;
84
85
  }
85
86
  }
@@ -109,7 +110,9 @@ function replaceCssImportWithNamedImports(ast, cssBinding, source, imports) {
109
110
  }
110
111
  function upsertNamedImports(ast, source, imports) {
111
112
  if (imports.length === 0) return;
112
- const existing = findImportDeclaration(ast, source);
113
+ const existing = ast.program.body.find(
114
+ (node) => t.isImportDeclaration(node) && node.source.value === source && node.importKind !== "type" && !node.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec))
115
+ );
113
116
  if (!existing) {
114
117
  const importDecl = t.importDeclaration(imports.map(toImportSpecifier), t.stringLiteral(source));
115
118
  ast.program.body.splice(findLastImportIndex(ast) + 1, 0, importDecl);
@@ -117,7 +120,7 @@ function upsertNamedImports(ast, source, imports) {
117
120
  }
118
121
  for (const entry of imports) {
119
122
  const exists = existing.specifiers.some((spec) => {
120
- return t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: entry.importedName });
123
+ return t.isImportSpecifier(spec) && spec.importKind !== "type" && t.isIdentifier(spec.imported, { name: entry.importedName });
121
124
  });
122
125
  if (!exists) existing.specifiers.push(toImportSpecifier(entry));
123
126
  }
@@ -183,6 +186,15 @@ function memberPropertyName(node) {
183
186
  if (node.computed && t.isStringLiteral(node.property)) return node.property.value;
184
187
  return null;
185
188
  }
189
+ function hoistTypeOnlyImportKind(node) {
190
+ if (node.importKind === "type") return;
191
+ const typeOnly = node.specifiers.every((spec) => t.isImportSpecifier(spec) && spec.importKind === "type");
192
+ if (!typeOnly) return;
193
+ node.importKind = "type";
194
+ for (const spec of node.specifiers) {
195
+ if (t.isImportSpecifier(spec)) spec.importKind = null;
196
+ }
197
+ }
186
198
  function toImportSpecifier(entry) {
187
199
  return t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName));
188
200
  }
@@ -1874,6 +1886,55 @@ function flattenWhenObjectParts(resolved) {
1874
1886
  return segments;
1875
1887
  }
1876
1888
 
1889
+ // src/css-order.ts
1890
+ function ruleSortKey(priority, className, atRulePrelude2) {
1891
+ const widthInterval = atRulePrelude2 === void 0 ? null : parseWidthInterval(atRulePrelude2);
1892
+ return { priority, className, widthInterval };
1893
+ }
1894
+ function compareRuleSortKeys(a, b) {
1895
+ return a.priority - b.priority || compareWidthIntervals(a.widthInterval, b.widthInterval) || compareClassNames(a.className, b.className);
1896
+ }
1897
+ function compareClassNames(a, b) {
1898
+ return a < b ? -1 : a > b ? 1 : 0;
1899
+ }
1900
+ function atRulePrelude(cssText) {
1901
+ if (!cssText.startsWith("@")) return void 0;
1902
+ const brace = cssText.indexOf("{");
1903
+ return brace === -1 ? void 0 : cssText.slice(0, brace).trim();
1904
+ }
1905
+ function parseWidthInterval(prelude) {
1906
+ if (/,|\bnot\b|\bor\b|[<>]/.test(prelude)) return null;
1907
+ const terms = Array.from(prelude.matchAll(/\((min|max)-width:\s*([^)]*)\)/g));
1908
+ if (terms.length === 0) return null;
1909
+ let lo = 0;
1910
+ let hi = Infinity;
1911
+ for (const term of terms) {
1912
+ const px = parsePxLength(term[2]);
1913
+ if (px === null) return null;
1914
+ if (term[1] === "min") {
1915
+ lo = Math.max(lo, px);
1916
+ } else {
1917
+ hi = Math.min(hi, px);
1918
+ }
1919
+ }
1920
+ return { lo, hi };
1921
+ }
1922
+ function parsePxLength(value) {
1923
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(px)?$/);
1924
+ if (!match) return null;
1925
+ if (match[2] === void 0 && Number(match[1]) !== 0) return null;
1926
+ return Number(match[1]);
1927
+ }
1928
+ function compareWidthIntervals(a, b) {
1929
+ if (a === null || b === null) {
1930
+ return (a === null ? 1 : 0) - (b === null ? 1 : 0);
1931
+ }
1932
+ const widthA = a.hi - a.lo;
1933
+ const widthB = b.hi - b.lo;
1934
+ if (widthA !== widthB) return widthA > widthB ? -1 : 1;
1935
+ return a.lo - b.lo;
1936
+ }
1937
+
1877
1938
  // src/plugin/property-priorities.ts
1878
1939
  var longHandPhysical = /* @__PURE__ */ new Set();
1879
1940
  var longHandLogical = /* @__PURE__ */ new Set();
@@ -2428,13 +2489,6 @@ function computeRulePriority(rule) {
2428
2489
  function isVariableRule(rule) {
2429
2490
  return rule.declarations.some((d) => d.cssVarName !== void 0);
2430
2491
  }
2431
- function ruleSortKey(priority, className, atRulePrelude2) {
2432
- const widthInterval = atRulePrelude2 === void 0 ? null : parseWidthInterval(atRulePrelude2);
2433
- return { priority, className, widthInterval };
2434
- }
2435
- function compareRuleSortKeys(a, b) {
2436
- return a.priority - b.priority || compareWidthIntervals(a.widthInterval, b.widthInterval) || compareClassNames(a.className, b.className);
2437
- }
2438
2492
  function sortRulesByPriority(rules) {
2439
2493
  const decorated = Array.from(rules, (rule) => {
2440
2494
  const priority = computeRulePriority(rule);
@@ -2443,40 +2497,83 @@ function sortRulesByPriority(rules) {
2443
2497
  decorated.sort((a, b) => compareRuleSortKeys(a.key, b.key));
2444
2498
  return decorated.map((d) => ({ rule: d.rule, priority: d.priority }));
2445
2499
  }
2446
- function compareClassNames(a, b) {
2447
- return a < b ? -1 : a > b ? 1 : 0;
2448
- }
2449
- function parseWidthInterval(prelude) {
2450
- if (/,|\bnot\b|\bor\b|[<>]/.test(prelude)) return null;
2451
- const terms = Array.from(prelude.matchAll(/\((min|max)-width:\s*([^)]*)\)/g));
2452
- if (terms.length === 0) return null;
2453
- let lo = 0;
2454
- let hi = Infinity;
2455
- for (const term of terms) {
2456
- const px = parsePxLength(term[2]);
2457
- if (px === null) return null;
2458
- if (term[1] === "min") {
2459
- lo = Math.max(lo, px);
2460
- } else {
2461
- hi = Math.min(hi, px);
2500
+
2501
+ // src/plugin/truss-css.ts
2502
+ var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
2503
+ var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
2504
+ var ARBITRARY_START_RE = /^\/\* @truss arbitrary:start \*\/$/;
2505
+ var ARBITRARY_END_RE = /^\/\* @truss arbitrary:end \*\/$/;
2506
+ var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
2507
+ function parseTrussCss(cssText) {
2508
+ const lines = cssText.split("\n");
2509
+ const rules = [];
2510
+ const properties = [];
2511
+ const arbitraryCssBlocks = [];
2512
+ let i = 0;
2513
+ function takeAnnotatedLine() {
2514
+ i++;
2515
+ while (i < lines.length && lines[i].trim() === "") i++;
2516
+ return i < lines.length ? lines[i].trim() : null;
2517
+ }
2518
+ while (i < lines.length) {
2519
+ const line = lines[i].trim();
2520
+ const ruleMatch = RULE_ANNOTATION_RE.exec(line);
2521
+ if (ruleMatch) {
2522
+ const cssText2 = takeAnnotatedLine();
2523
+ if (cssText2 !== null) {
2524
+ rules.push({ priority: parseFloat(ruleMatch[1]), className: ruleMatch[2], cssText: cssText2 });
2525
+ }
2526
+ i++;
2527
+ continue;
2462
2528
  }
2529
+ if (PROPERTY_ANNOTATION_RE.test(line)) {
2530
+ const propLine = takeAnnotatedLine();
2531
+ const varMatch = propLine === null ? null : PROPERTY_VAR_RE.exec(propLine);
2532
+ if (propLine !== null && varMatch) {
2533
+ properties.push({ cssText: propLine, varName: varMatch[1] });
2534
+ }
2535
+ i++;
2536
+ continue;
2537
+ }
2538
+ if (ARBITRARY_START_RE.test(line)) {
2539
+ i++;
2540
+ const blockLines = [];
2541
+ while (i < lines.length && !ARBITRARY_END_RE.test(lines[i].trim())) {
2542
+ blockLines.push(lines[i]);
2543
+ i++;
2544
+ }
2545
+ const blockText = blockLines.join("\n").trim();
2546
+ if (blockText.length > 0) {
2547
+ arbitraryCssBlocks.push({ cssText: blockText });
2548
+ }
2549
+ if (i < lines.length && ARBITRARY_END_RE.test(lines[i].trim())) {
2550
+ i++;
2551
+ }
2552
+ continue;
2553
+ }
2554
+ i++;
2463
2555
  }
2464
- return { lo, hi };
2556
+ return { rules, properties, arbitraryCssBlocks };
2465
2557
  }
2466
- function parsePxLength(value) {
2467
- const match = value.trim().match(/^(\d+(?:\.\d+)?)(px)?$/);
2468
- if (!match) return null;
2469
- if (match[2] === void 0 && Number(match[1]) !== 0) return null;
2470
- return Number(match[1]);
2558
+ function serializeTrussCss(css) {
2559
+ const lines = [];
2560
+ for (const rule of css.rules) {
2561
+ lines.push(`/* @truss p:${rule.priority} c:${rule.className} */`, rule.cssText);
2562
+ }
2563
+ for (const prop of css.properties) {
2564
+ lines.push(`/* @truss @property */`, prop.cssText);
2565
+ }
2566
+ for (const block of css.arbitraryCssBlocks) {
2567
+ lines.push(annotateArbitraryCssBlock(block.cssText));
2568
+ }
2569
+ return lines.join("\n");
2471
2570
  }
2472
- function compareWidthIntervals(a, b) {
2473
- if (a === null || b === null) {
2474
- return (a === null ? 1 : 0) - (b === null ? 1 : 0);
2571
+ function annotateArbitraryCssBlock(cssText) {
2572
+ const trimmed = cssText.trim();
2573
+ if (trimmed.length === 0) {
2574
+ return "";
2475
2575
  }
2476
- const widthA = a.hi - a.lo;
2477
- const widthB = b.hi - b.lo;
2478
- if (widthA !== widthB) return widthA > widthB ? -1 : 1;
2479
- return a.lo - b.lo;
2576
+ return ["/* @truss arbitrary:start */", trimmed, "/* @truss arbitrary:end */"].join("\n");
2480
2577
  }
2481
2578
 
2482
2579
  // src/plugin/emit-css.ts
@@ -2538,22 +2635,28 @@ function whenSelectorFor(whenPseudo) {
2538
2635
  pseudo: whenPseudo.pseudo
2539
2636
  };
2540
2637
  }
2541
- function generateCssText(rules) {
2638
+ function generateCssData(rules) {
2542
2639
  const sorted = sortRulesByPriority(rules.values());
2543
- const lines = [];
2544
- for (const { rule, priority } of sorted) {
2545
- lines.push(`/* @truss p:${priority} c:${rule.className} */`);
2546
- lines.push(formatRule(rule));
2547
- }
2640
+ const css = {
2641
+ rules: sorted.map((entry) => ({
2642
+ priority: entry.priority,
2643
+ className: entry.rule.className,
2644
+ cssText: formatRule(entry.rule)
2645
+ })),
2646
+ properties: [],
2647
+ arbitraryCssBlocks: []
2648
+ };
2548
2649
  for (const { rule } of sorted) {
2549
2650
  for (const declaration of rule.declarations) {
2550
2651
  if (declaration.cssVarName) {
2551
- lines.push(`/* @truss @property */`);
2552
- lines.push(`@property ${declaration.cssVarName} { syntax: "*"; inherits: false; }`);
2652
+ css.properties.push({
2653
+ varName: declaration.cssVarName,
2654
+ cssText: `@property ${declaration.cssVarName} { syntax: "*"; inherits: false; }`
2655
+ });
2553
2656
  }
2554
2657
  }
2555
2658
  }
2556
- return lines.join("\n");
2659
+ return css;
2557
2660
  }
2558
2661
  function formatRule(rule) {
2559
2662
  const duplicateClassName = !!rule.mediaQuery;
@@ -2798,6 +2901,38 @@ ${body}
2798
2901
  import * as t15 from "@babel/types";
2799
2902
  import { basename } from "path";
2800
2903
 
2904
+ // src/plugin/test-css.ts
2905
+ import { parse as parse2 } from "css-tree";
2906
+ function createTestCssPayload(css) {
2907
+ const payload = {};
2908
+ if (css.rules.length > 0) {
2909
+ payload.rules = css.rules.map((rule) => {
2910
+ const atRule = atRulePrelude(rule.cssText);
2911
+ return { ...rule, ...atRule === void 0 ? {} : { atRule } };
2912
+ });
2913
+ }
2914
+ if (css.properties.length > 0) payload.properties = css.properties;
2915
+ const arbitraryRules = css.arbitraryCssBlocks.flatMap((block) => splitArbitraryCss(block.cssText));
2916
+ if (arbitraryRules.length > 0) payload.arbitraryRules = arbitraryRules;
2917
+ return payload;
2918
+ }
2919
+ function splitArbitraryCss(cssText) {
2920
+ const root = parse2(cssText, {
2921
+ context: "stylesheet",
2922
+ positions: true,
2923
+ parseRulePrelude: false,
2924
+ parseAtrulePrelude: false,
2925
+ parseValue: false
2926
+ });
2927
+ const rules = [];
2928
+ root.children.forEach((node) => {
2929
+ if (node.type === "Rule" || node.type === "Atrule") {
2930
+ rules.push(cssText.slice(node.loc.start.offset, node.loc.end.offset));
2931
+ }
2932
+ });
2933
+ return rules;
2934
+ }
2935
+
2801
2936
  // src/plugin/emit-style-hash.ts
2802
2937
  import * as t13 from "@babel/types";
2803
2938
  function buildStyleHashProperties(segments, mapping, maybeIncHelperName, maybeCssVarHelperName) {
@@ -3264,7 +3399,8 @@ function transformTruss(code, filename, mapping, options = {}) {
3264
3399
  if (sites.length === 0 && !hasCssPropsCall && !hasBuildtimeJsxCssAttribute) return null;
3265
3400
  const chains = sites.map((s) => s.resolvedChain);
3266
3401
  const { rules, needsMaybeInc, needsMaybeCssVar } = collectAtomicRules(chains, mapping);
3267
- const cssText = generateCssText(rules);
3402
+ const cssData = generateCssData(rules);
3403
+ const cssText = serializeTrussCss(cssData);
3268
3404
  const runtime = createRuntimeHelpers(ast, usedTopLevelNames);
3269
3405
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
3270
3406
  const maybeCssVarHelperName = needsMaybeCssVar ? runtime.use("maybeCssVar") : null;
@@ -3310,7 +3446,9 @@ function transformTruss(code, filename, mapping, options = {}) {
3310
3446
  }
3311
3447
  if (options.injectCss && cssText.length > 0) {
3312
3448
  declarationsToInsert.push(
3313
- t15.expressionStatement(t15.callExpression(t15.identifier("__injectTrussCSS"), [t15.stringLiteral(cssText)]))
3449
+ t15.expressionStatement(
3450
+ t15.callExpression(t15.identifier("__injectTrussCSS"), [t15.valueToNode(createTestCssPayload(cssData))])
3451
+ )
3314
3452
  );
3315
3453
  }
3316
3454
  for (const { message, line } of errorMessages) {
@@ -3429,74 +3567,11 @@ function findLastImportLine(lines) {
3429
3567
 
3430
3568
  // src/plugin/merge-css.ts
3431
3569
  import { readFileSync as readFileSync2 } from "fs";
3432
- var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
3433
- var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
3434
- var ARBITRARY_START_RE = /^\/\* @truss arbitrary:start \*\/$/;
3435
- var ARBITRARY_END_RE = /^\/\* @truss arbitrary:end \*\/$/;
3436
- var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
3437
- function parseTrussCss(cssText) {
3438
- const lines = cssText.split("\n");
3439
- const rules = [];
3440
- const properties = [];
3441
- const arbitraryCssBlocks = [];
3442
- let i = 0;
3443
- function takeAnnotatedLine() {
3444
- i++;
3445
- while (i < lines.length && lines[i].trim() === "") i++;
3446
- return i < lines.length ? lines[i].trim() : null;
3447
- }
3448
- while (i < lines.length) {
3449
- const line = lines[i].trim();
3450
- const ruleMatch = RULE_ANNOTATION_RE.exec(line);
3451
- if (ruleMatch) {
3452
- const cssText2 = takeAnnotatedLine();
3453
- if (cssText2 !== null) {
3454
- rules.push({ priority: parseFloat(ruleMatch[1]), className: ruleMatch[2], cssText: cssText2 });
3455
- }
3456
- i++;
3457
- continue;
3458
- }
3459
- if (PROPERTY_ANNOTATION_RE.test(line)) {
3460
- const propLine = takeAnnotatedLine();
3461
- const varMatch = propLine === null ? null : PROPERTY_VAR_RE.exec(propLine);
3462
- if (propLine !== null && varMatch) {
3463
- properties.push({ cssText: propLine, varName: varMatch[1] });
3464
- }
3465
- i++;
3466
- continue;
3467
- }
3468
- if (ARBITRARY_START_RE.test(line)) {
3469
- i++;
3470
- const blockLines = [];
3471
- while (i < lines.length && !ARBITRARY_END_RE.test(lines[i].trim())) {
3472
- blockLines.push(lines[i]);
3473
- i++;
3474
- }
3475
- const blockText = blockLines.join("\n").trim();
3476
- if (blockText.length > 0) {
3477
- arbitraryCssBlocks.push({ cssText: blockText });
3478
- }
3479
- if (i < lines.length && ARBITRARY_END_RE.test(lines[i].trim())) {
3480
- i++;
3481
- }
3482
- continue;
3483
- }
3484
- i++;
3485
- }
3486
- return { rules, properties, arbitraryCssBlocks };
3487
- }
3488
3570
  function readTrussCss(filePath) {
3489
3571
  const content = readFileSync2(filePath, "utf8");
3490
3572
  return parseTrussCss(content);
3491
3573
  }
3492
- function annotateArbitraryCssBlock(cssText) {
3493
- const trimmed = cssText.trim();
3494
- if (trimmed.length === 0) {
3495
- return "";
3496
- }
3497
- return ["/* @truss arbitrary:start */", trimmed, "/* @truss arbitrary:end */"].join("\n");
3498
- }
3499
- function mergeTrussCss(sources) {
3574
+ function mergeTrussCssData(sources) {
3500
3575
  const seenClasses = /* @__PURE__ */ new Set();
3501
3576
  const allRules = [];
3502
3577
  const seenProperties = /* @__PURE__ */ new Set();
@@ -3521,24 +3596,11 @@ function mergeTrussCss(sources) {
3521
3596
  return { rule, key: ruleSortKey(rule.priority, rule.className, atRulePrelude(rule.cssText)) };
3522
3597
  });
3523
3598
  decorated.sort((a, b) => compareRuleSortKeys(a.key, b.key));
3524
- const lines = [];
3525
- for (const entry of decorated) {
3526
- lines.push(`/* @truss p:${entry.rule.priority} c:${entry.rule.className} */`);
3527
- lines.push(entry.rule.cssText);
3528
- }
3529
- for (const prop of allProperties) {
3530
- lines.push(`/* @truss @property */`);
3531
- lines.push(prop.cssText);
3532
- }
3533
- for (const block of allArbitraryCssBlocks) {
3534
- lines.push(annotateArbitraryCssBlock(block.cssText));
3535
- }
3536
- return lines.join("\n");
3537
- }
3538
- function atRulePrelude(cssText) {
3539
- if (!cssText.startsWith("@")) return void 0;
3540
- const brace = cssText.indexOf("{");
3541
- return brace === -1 ? void 0 : cssText.slice(0, brace).trim();
3599
+ return {
3600
+ rules: decorated.map((entry) => entry.rule),
3601
+ properties: allProperties,
3602
+ arbitraryCssBlocks: allArbitraryCssBlocks
3603
+ };
3542
3604
  }
3543
3605
 
3544
3606
  // src/plugin/transform-session.ts
@@ -3569,6 +3631,7 @@ function createTrussTransformSession(options) {
3569
3631
  libraryCache = null;
3570
3632
  }
3571
3633
  function updateArbitraryCssRegistry(sourcePath, sourceCode) {
3634
+ sourcePath = resolve2(sourcePath).replace(/\\/g, "/");
3572
3635
  const css = transformCssTs(sourceCode, sourcePath, ensureMapping()).trim();
3573
3636
  if (css.length > 0) {
3574
3637
  const prev = arbitraryCssRegistry.get(sourcePath);
@@ -3597,12 +3660,11 @@ function createTrussTransformSession(options) {
3597
3660
  }
3598
3661
  function collectCss() {
3599
3662
  const mapping2 = ensureMapping();
3600
- const appCssParts = [generateCssText(cssRegistry)];
3601
- const allArbitrary = Array.from(arbitraryCssRegistry.values()).join("\n\n");
3602
- appCssParts.push(annotateArbitraryCssBlock(allArbitrary));
3603
- const appCss = appCssParts.filter((part) => part.length > 0).join("\n");
3663
+ const appCss = generateCssData(cssRegistry);
3664
+ const allArbitrary = Array.from(arbitraryCssRegistry.entries()).sort((a, b) => compareClassNames(a[0], b[0])).map((entry) => entry[1]).join("\n\n");
3665
+ if (allArbitrary.length > 0) appCss.arbitraryCssBlocks.push({ cssText: allArbitrary });
3604
3666
  const libs = loadLibraries();
3605
- const body = libs.length === 0 ? appCss : mergeTrussCss([...libs, parseTrussCss(appCss)]);
3667
+ const body = serializeTrussCss(libs.length === 0 ? appCss : mergeTrussCssData([...libs, appCss]));
3606
3668
  if (body.length === 0) return "";
3607
3669
  return `${rootSpacingPreludeCss(mapping2.increment)}
3608
3670
  ${body}`;
@@ -3610,8 +3672,16 @@ ${body}`;
3610
3672
  function hasCss() {
3611
3673
  return cssRegistry.size > 0 || arbitraryCssRegistry.size > 0 || libraryPaths.length > 0;
3612
3674
  }
3675
+ function collectTestCss() {
3676
+ return createTestCssPayload(mergeTrussCssData(loadLibraries()));
3677
+ }
3678
+ function getArbitraryCss(sourcePath) {
3679
+ return arbitraryCssRegistry.get(resolve2(sourcePath).replace(/\\/g, "/")) ?? "";
3680
+ }
3613
3681
  return {
3614
3682
  collectCss,
3683
+ collectTestCss,
3684
+ getArbitraryCss,
3615
3685
  ensureMapping,
3616
3686
  hasCss,
3617
3687
  reset,
@@ -3620,6 +3690,9 @@ ${body}`;
3620
3690
  };
3621
3691
  }
3622
3692
 
3693
+ // src/plugin/index.ts
3694
+ import * as t16 from "@babel/types";
3695
+
3623
3696
  // src/plugin/esbuild-plugin.ts
3624
3697
  import { readFileSync as readFileSync3, writeFileSync, mkdirSync } from "fs";
3625
3698
  import { resolve as resolve3, join } from "path";
@@ -3667,7 +3740,10 @@ function loaderForPath(filePath) {
3667
3740
 
3668
3741
  // src/plugin/index.ts
3669
3742
  var VIRTUAL_CSS_PREFIX = "\0truss-css:";
3743
+ var VIRTUAL_TEST_CSS_PREFIX = "\0truss-test-css:";
3670
3744
  var CSS_TS_QUERY = "?truss-css";
3745
+ var RUNTIME_MODULE2 = "@homebound/truss/runtime";
3746
+ var INJECT_CSS_HELPER = "__injectTrussCSS";
3671
3747
  var TRUSS_CSS_PLACEHOLDER = "__TRUSS_CSS_HASH__";
3672
3748
  var VIRTUAL_CSS_ENDPOINT = "/virtual:truss.css";
3673
3749
  var VIRTUAL_RUNTIME_ID = "virtual:truss:runtime";
@@ -3756,6 +3832,7 @@ function trussPlugin(opts) {
3756
3832
  if (!source.endsWith(CSS_TS_QUERY)) return null;
3757
3833
  const absolutePath = resolveImportPath(source.slice(0, -CSS_TS_QUERY.length), importer, projectRoot);
3758
3834
  if (!existsSync2(absolutePath)) return null;
3835
+ if (isTest) return VIRTUAL_TEST_CSS_PREFIX + absolutePath;
3759
3836
  return VIRTUAL_CSS_PREFIX + absolutePath.slice(0, -3);
3760
3837
  },
3761
3838
  load(id) {
@@ -3789,11 +3866,30 @@ function trussPlugin(opts) {
3789
3866
  `;
3790
3867
  }
3791
3868
  if (id === RESOLVED_VIRTUAL_TEST_CSS_ID) {
3792
- const css = session.collectCss();
3869
+ const payload = {
3870
+ ...session.collectTestCss(),
3871
+ source: "libraries",
3872
+ order: 0,
3873
+ prelude: rootSpacingPreludeCss(session.ensureMapping().increment)
3874
+ };
3793
3875
  return `
3794
3876
  import { __injectTrussCSS } from "@homebound/truss/runtime";
3795
3877
 
3796
- __injectTrussCSS(${JSON.stringify(css)});
3878
+ __injectTrussCSS(${JSON.stringify(payload)});
3879
+ `;
3880
+ }
3881
+ if (id.startsWith(VIRTUAL_TEST_CSS_PREFIX)) {
3882
+ const sourcePath2 = canonicalSourcePath(id.slice(VIRTUAL_TEST_CSS_PREFIX.length));
3883
+ session.updateArbitraryCssRegistry(sourcePath2, readFileSync4(sourcePath2, "utf8"));
3884
+ const payload = {
3885
+ arbitraryRules: splitArbitraryCss(session.getArbitraryCss(sourcePath2)),
3886
+ source: sourcePath2
3887
+ };
3888
+ return `
3889
+ import "${VIRTUAL_TEST_CSS_ID}";
3890
+ import { __injectTrussCSS } from "@homebound/truss/runtime";
3891
+
3892
+ __injectTrussCSS(${JSON.stringify(payload)});
3797
3893
  `;
3798
3894
  }
3799
3895
  if (!id.startsWith(VIRTUAL_CSS_PREFIX)) return null;
@@ -3803,16 +3899,21 @@ __injectTrussCSS(${JSON.stringify(css)});
3803
3899
  return `/* [truss] ${sourcePath} \u2014 included via truss.css */`;
3804
3900
  },
3805
3901
  transform(code, id) {
3902
+ if (id.startsWith(VIRTUAL_TEST_CSS_PREFIX)) return null;
3806
3903
  if (!/\.[cm]?[jt]sx?(\?|$)/.test(id)) return null;
3807
3904
  const fileId = stripQueryAndHash(id);
3808
3905
  if (isNodeModulesFile(fileId)) return null;
3809
3906
  const rewrittenImports = rewriteCssTsImports(code, id);
3810
- const shouldBootstrapTestCss = isTest && libraryPaths.length > 0;
3907
+ const shouldBootstrapTestCss = isTest;
3811
3908
  const transformedCode = shouldBootstrapTestCss ? `${rewrittenImports.code}
3812
3909
  import "${VIRTUAL_TEST_CSS_ID}";` : rewrittenImports.code;
3813
3910
  const importsOnlyResult = rewrittenImports.changed || shouldBootstrapTestCss ? { code: transformedCode, map: null } : null;
3814
3911
  if (fileId.endsWith(".css.ts")) {
3815
3912
  session.updateArbitraryCssRegistry(fileId, code);
3913
+ if (isTest) {
3914
+ const css = session.getArbitraryCss(fileId);
3915
+ return { code: appendTestCssInjection(transformedCode, fileId, css), map: null };
3916
+ }
3816
3917
  return importsOnlyResult;
3817
3918
  }
3818
3919
  const hasCssDsl = rewrittenImports.code.includes("Css") || rewrittenImports.code.includes("css=");
@@ -3873,6 +3974,30 @@ function stripQueryAndHash(id) {
3873
3974
  function isNodeModulesFile(filePath) {
3874
3975
  return filePath.replace(/\\/g, "/").includes("/node_modules/");
3875
3976
  }
3977
+ function canonicalSourcePath(filePath) {
3978
+ return resolve4(filePath).replace(/\\/g, "/");
3979
+ }
3980
+ function appendTestCssInjection(code, fileId, css) {
3981
+ const ast = parseModule(code, fileId);
3982
+ let usedTopLevelNames = /* @__PURE__ */ new Set();
3983
+ traverse(ast, {
3984
+ Program(path) {
3985
+ usedTopLevelNames = new Set(Object.keys(path.scope.bindings));
3986
+ path.stop();
3987
+ }
3988
+ });
3989
+ const existing = findNamedImportBinding(ast, INJECT_CSS_HELPER, RUNTIME_MODULE2);
3990
+ const localName = existing ?? reservePreferredName(usedTopLevelNames, INJECT_CSS_HELPER);
3991
+ if (!existing) upsertNamedImports(ast, RUNTIME_MODULE2, [{ importedName: INJECT_CSS_HELPER, localName }]);
3992
+ ast.program.body.push(
3993
+ t16.expressionStatement(
3994
+ t16.callExpression(t16.identifier(localName), [
3995
+ t16.valueToNode({ arbitraryRules: splitArbitraryCss(css), source: canonicalSourcePath(fileId) })
3996
+ ])
3997
+ )
3998
+ );
3999
+ return generate(ast, { sourceFileName: fileId }).code;
4000
+ }
3876
4001
  export {
3877
4002
  loadMapping,
3878
4003
  trussEsbuildPlugin,