@homebound/truss 2.0.0-next.5 → 2.0.0-next.9

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.
@@ -58,9 +58,9 @@ interface TrussVitePlugin {
58
58
  * Vite plugin that transforms `Css.*.$` expressions from truss's CssBuilder DSL
59
59
  * into file-local `stylex.create()` + `stylex.props()` calls.
60
60
  *
61
- * Also supports `.css.ts` files: a `.css.ts` file with `export default { ".selector": Css.blue.$ }`
62
- * is transformed into a virtual CSS module. Imports of `.css.ts` files are rewritten
63
- * to load the generated CSS.
61
+ * Also supports `.css.ts` files: a `.css.ts` file with
62
+ * `export const css = { ".selector": Css.blue.$ }` can keep other runtime exports,
63
+ * while imports are supplemented with a virtual CSS side-effect module.
64
64
  *
65
65
  * Must be placed BEFORE the StyleX unplugin in the plugins array so that
66
66
  * StyleX's babel plugin can process the generated `stylex.create()` calls.
@@ -1130,6 +1130,44 @@ function buildMaybeIncDeclaration(helperName, increment) {
1130
1130
  t2.variableDeclarator(t2.identifier(helperName), t2.arrowFunctionExpression([incParam], body))
1131
1131
  ]);
1132
1132
  }
1133
+ function buildMergePropsDeclaration(helperName, stylexNamespaceName) {
1134
+ const explicitClassNameParam = t2.identifier("explicitClassName");
1135
+ const stylesRestParam = t2.restElement(t2.identifier("styles"));
1136
+ const sxId = t2.identifier("sx");
1137
+ return t2.functionDeclaration(
1138
+ t2.identifier(helperName),
1139
+ [explicitClassNameParam, stylesRestParam],
1140
+ t2.blockStatement([
1141
+ t2.variableDeclaration("const", [
1142
+ t2.variableDeclarator(
1143
+ sxId,
1144
+ t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("props")), [
1145
+ t2.spreadElement(t2.identifier("styles"))
1146
+ ])
1147
+ )
1148
+ ]),
1149
+ t2.returnStatement(
1150
+ t2.objectExpression([
1151
+ t2.spreadElement(sxId),
1152
+ t2.objectProperty(
1153
+ t2.identifier("className"),
1154
+ t2.callExpression(
1155
+ t2.memberExpression(
1156
+ t2.binaryExpression(
1157
+ "+",
1158
+ t2.binaryExpression("+", explicitClassNameParam, t2.stringLiteral(" ")),
1159
+ t2.logicalExpression("||", t2.memberExpression(sxId, t2.identifier("className")), t2.stringLiteral(""))
1160
+ ),
1161
+ t2.identifier("trim")
1162
+ ),
1163
+ []
1164
+ )
1165
+ )
1166
+ ])
1167
+ )
1168
+ ])
1169
+ );
1170
+ }
1133
1171
  function buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties) {
1134
1172
  const createCall = t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("create")), [
1135
1173
  t2.objectExpression(createProperties)
@@ -1191,54 +1229,22 @@ function rewriteExpressionSites(options) {
1191
1229
  t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("props")),
1192
1230
  propsArgs
1193
1231
  );
1194
- const openingElement = cssAttrPath.parentPath;
1195
- let existingClassNameExpr = null;
1196
- if (openingElement && openingElement.isJSXOpeningElement()) {
1197
- const attrs = openingElement.node.attributes;
1198
- for (let i = 0; i < attrs.length; i++) {
1199
- const attr = attrs[i];
1200
- if (t3.isJSXAttribute(attr) && t3.isJSXIdentifier(attr.name, { name: "className" })) {
1201
- if (t3.isStringLiteral(attr.value)) {
1202
- existingClassNameExpr = attr.value;
1203
- } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1204
- existingClassNameExpr = attr.value.expression;
1205
- }
1206
- attrs.splice(i, 1);
1207
- break;
1208
- }
1209
- }
1210
- }
1211
- let spreadExpr;
1212
- if (existingClassNameExpr) {
1213
- const rId = t3.identifier("__r");
1214
- const mergedClassName = t3.callExpression(
1215
- t3.memberExpression(
1216
- t3.binaryExpression(
1217
- "+",
1218
- t3.binaryExpression("+", existingClassNameExpr, t3.stringLiteral(" ")),
1219
- t3.logicalExpression("||", t3.memberExpression(rId, t3.identifier("className")), t3.stringLiteral(""))
1220
- ),
1221
- t3.identifier("trim")
1222
- ),
1223
- []
1224
- );
1225
- spreadExpr = t3.callExpression(
1226
- t3.arrowFunctionExpression(
1227
- [rId],
1228
- t3.objectExpression([t3.spreadElement(rId), t3.objectProperty(t3.identifier("className"), mergedClassName)])
1229
- ),
1230
- [propsCall]
1231
- );
1232
- } else {
1233
- spreadExpr = propsCall;
1234
- }
1235
- cssAttrPath.replaceWith(t3.jsxSpreadAttribute(spreadExpr));
1232
+ cssAttrPath.replaceWith(
1233
+ t3.jsxSpreadAttribute(
1234
+ buildCssSpreadExpression(cssAttrPath, propsCall, options.mergePropsHelperName, options.needsMergePropsHelper)
1235
+ )
1236
+ );
1236
1237
  continue;
1237
1238
  }
1238
1239
  site.path.replaceWith(t3.arrayExpression(propsArgs));
1239
1240
  }
1240
1241
  rewriteStyleObjectExpressions(options.ast);
1241
- rewriteCssArrayExpressions(options.ast, options.stylexNamespaceName);
1242
+ rewriteCssAttributeExpressions(
1243
+ options.ast,
1244
+ options.stylexNamespaceName,
1245
+ options.mergePropsHelperName,
1246
+ options.needsMergePropsHelper
1247
+ );
1242
1248
  }
1243
1249
  function getCssAttributePath(path) {
1244
1250
  const parentPath = path.parentPath;
@@ -1315,42 +1321,107 @@ function buildPropsArgs(segments, options) {
1315
1321
  }
1316
1322
  return args;
1317
1323
  }
1318
- function rewriteCssArrayExpressions(ast, stylexNamespaceName) {
1324
+ function rewriteCssAttributeExpressions(ast, stylexNamespaceName, mergePropsHelperName, needsMergePropsHelper) {
1319
1325
  traverse(ast, {
1320
1326
  JSXAttribute(path) {
1321
1327
  if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1322
1328
  const value = path.node.value;
1323
1329
  if (!t3.isJSXExpressionContainer(value)) return;
1324
- const expr = value.expression;
1325
- if (!t3.isArrayExpression(expr)) return;
1326
- const propsArgs = [];
1327
- for (const el of expr.elements) {
1328
- if (t3.isSpreadElement(el)) {
1329
- const arg = el.argument;
1330
- if (t3.isArrayExpression(arg)) {
1331
- for (const inner of arg.elements) {
1332
- if (!inner) continue;
1333
- if (t3.isSpreadElement(inner)) {
1334
- propsArgs.push(t3.spreadElement(inner.argument));
1335
- } else {
1336
- propsArgs.push(inner);
1337
- }
1338
- }
1339
- } else {
1340
- propsArgs.push(t3.spreadElement(arg));
1341
- }
1342
- } else if (el) {
1343
- propsArgs.push(el);
1344
- }
1345
- }
1330
+ if (!t3.isExpression(value.expression)) return;
1331
+ const propsArgs = buildStyleObjectPropsArgs(value.expression, path) ?? buildStyleArrayLikePropsArgsFromExpression(value.expression, path);
1332
+ if (!propsArgs) return;
1346
1333
  const propsCall = t3.callExpression(
1347
1334
  t3.memberExpression(t3.identifier(stylexNamespaceName), t3.identifier("props")),
1348
1335
  propsArgs
1349
1336
  );
1350
- path.replaceWith(t3.jsxSpreadAttribute(propsCall));
1337
+ path.replaceWith(
1338
+ t3.jsxSpreadAttribute(buildCssSpreadExpression(path, propsCall, mergePropsHelperName, needsMergePropsHelper))
1339
+ );
1351
1340
  }
1352
1341
  });
1353
1342
  }
1343
+ function buildCssSpreadExpression(path, propsCall, mergePropsHelperName, needsMergePropsHelper) {
1344
+ const existingClassNameExpr = removeExistingClassNameAttribute(path);
1345
+ if (!existingClassNameExpr) return propsCall;
1346
+ needsMergePropsHelper.current = true;
1347
+ return t3.callExpression(t3.identifier(mergePropsHelperName), [existingClassNameExpr, ...propsCall.arguments]);
1348
+ }
1349
+ function removeExistingClassNameAttribute(path) {
1350
+ const openingElement = path.parentPath;
1351
+ if (!openingElement || !openingElement.isJSXOpeningElement()) return null;
1352
+ const attrs = openingElement.node.attributes;
1353
+ for (let i = 0; i < attrs.length; i++) {
1354
+ const attr = attrs[i];
1355
+ if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: "className" })) continue;
1356
+ let classNameExpr = null;
1357
+ if (t3.isStringLiteral(attr.value)) {
1358
+ classNameExpr = attr.value;
1359
+ } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1360
+ classNameExpr = attr.value.expression;
1361
+ }
1362
+ attrs.splice(i, 1);
1363
+ return classNameExpr;
1364
+ }
1365
+ return null;
1366
+ }
1367
+ function buildStyleObjectPropsArgs(expr, path) {
1368
+ if (!t3.isObjectExpression(expr) || expr.properties.length === 0) return null;
1369
+ let sawStyleArray = false;
1370
+ const propsArgs = [];
1371
+ for (const prop of expr.properties) {
1372
+ if (!t3.isSpreadElement(prop)) return null;
1373
+ const normalizedArg = normalizeStyleArrayLikeExpression(prop.argument, path, /* @__PURE__ */ new Set());
1374
+ if (!normalizedArg) {
1375
+ propsArgs.push(t3.spreadElement(prop.argument));
1376
+ continue;
1377
+ }
1378
+ if (isStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1379
+ sawStyleArray = true;
1380
+ }
1381
+ const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path, /* @__PURE__ */ new Set());
1382
+ if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1383
+ propsArgs.push(...nestedArgs);
1384
+ } else {
1385
+ propsArgs.push(t3.spreadElement(normalizedArg));
1386
+ }
1387
+ }
1388
+ return sawStyleArray ? propsArgs : null;
1389
+ }
1390
+ function buildStyleArrayLikePropsArgsFromExpression(expr, path) {
1391
+ const normalizedExpr = normalizeStyleArrayLikeExpression(expr, path, /* @__PURE__ */ new Set());
1392
+ if (!normalizedExpr) return null;
1393
+ return buildStyleArrayLikePropsArgs(normalizedExpr, path, /* @__PURE__ */ new Set());
1394
+ }
1395
+ function buildStyleArrayLikePropsArgs(expr, path, seen) {
1396
+ if (seen.has(expr)) return null;
1397
+ seen.add(expr);
1398
+ if (t3.isArrayExpression(expr)) {
1399
+ const propsArgs = [];
1400
+ for (const el of expr.elements) {
1401
+ if (!el) continue;
1402
+ if (t3.isSpreadElement(el)) {
1403
+ const normalizedArg = normalizeStyleArrayLikeExpression(el.argument, path, /* @__PURE__ */ new Set());
1404
+ if (!normalizedArg) {
1405
+ propsArgs.push(t3.spreadElement(el.argument));
1406
+ continue;
1407
+ }
1408
+ const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path, seen);
1409
+ if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1410
+ propsArgs.push(...nestedArgs);
1411
+ } else {
1412
+ propsArgs.push(t3.spreadElement(normalizedArg));
1413
+ }
1414
+ continue;
1415
+ }
1416
+ propsArgs.push(el);
1417
+ }
1418
+ return propsArgs;
1419
+ }
1420
+ if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isConditionalExpression(expr)) {
1421
+ return [t3.spreadElement(expr)];
1422
+ }
1423
+ return null;
1424
+ }
1354
1425
  function rewriteStyleObjectExpressions(ast) {
1355
1426
  traverse(ast, {
1356
1427
  ObjectExpression(path) {
@@ -1368,7 +1439,7 @@ function tryBuildStyleArrayFromObject(path) {
1368
1439
  if (!t3.isSpreadElement(prop)) {
1369
1440
  return null;
1370
1441
  }
1371
- const normalizedArg = normalizeStyleArrayExpression(
1442
+ const normalizedArg = normalizeStyleArrayLikeExpression(
1372
1443
  prop.argument,
1373
1444
  path,
1374
1445
  /* @__PURE__ */ new Set()
@@ -1377,7 +1448,7 @@ function tryBuildStyleArrayFromObject(path) {
1377
1448
  if (!normalizedArg) {
1378
1449
  return null;
1379
1450
  }
1380
- if (isStyleArrayLikeExpression(normalizedArg, path, /* @__PURE__ */ new Set())) {
1451
+ if (isStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1381
1452
  sawStyleArray = true;
1382
1453
  }
1383
1454
  if (t3.isArrayExpression(normalizedArg)) {
@@ -1389,30 +1460,30 @@ function tryBuildStyleArrayFromObject(path) {
1389
1460
  if (!sawStyleArray) return null;
1390
1461
  return t3.arrayExpression(elements);
1391
1462
  }
1392
- function normalizeStyleArrayExpression(expr, path, seen) {
1463
+ function normalizeStyleArrayLikeExpression(expr, path, seen) {
1393
1464
  if (seen.has(expr)) return null;
1394
1465
  seen.add(expr);
1395
1466
  if (t3.isArrayExpression(expr)) return expr;
1396
1467
  if (t3.isConditionalExpression(expr)) {
1397
- const consequent = normalizeConditionalBranch(expr.consequent, path, seen);
1398
- const alternate = normalizeConditionalBranch(expr.alternate, path, seen);
1468
+ const consequent = normalizeStyleArrayLikeBranch(expr.consequent, path, seen);
1469
+ const alternate = normalizeStyleArrayLikeBranch(expr.alternate, path, seen);
1399
1470
  if (!consequent || !alternate) return null;
1400
1471
  return t3.conditionalExpression(expr.test, consequent, alternate);
1401
1472
  }
1402
1473
  if (t3.isIdentifier(expr) || t3.isMemberExpression(expr)) {
1403
1474
  const nestedSeen = new Set(seen);
1404
1475
  nestedSeen.delete(expr);
1405
- if (isStyleArrayLikeExpression(expr, path, nestedSeen)) return expr;
1476
+ if (isStyleArrayLike(expr, path, nestedSeen)) return expr;
1406
1477
  }
1407
1478
  return null;
1408
1479
  }
1409
- function normalizeConditionalBranch(expr, path, seen) {
1480
+ function normalizeStyleArrayLikeBranch(expr, path, seen) {
1410
1481
  if (isEmptyObjectExpression(expr)) {
1411
1482
  return t3.arrayExpression([]);
1412
1483
  }
1413
- return normalizeStyleArrayExpression(expr, path, seen);
1484
+ return normalizeStyleArrayLikeExpression(expr, path, seen);
1414
1485
  }
1415
- function isStyleArrayLikeExpression(expr, path, seen) {
1486
+ function isStyleArrayLike(expr, path, seen) {
1416
1487
  if (seen.has(expr)) return false;
1417
1488
  seen.add(expr);
1418
1489
  if (t3.isArrayExpression(expr)) return true;
@@ -1424,7 +1495,7 @@ function isStyleArrayLikeExpression(expr, path, seen) {
1424
1495
  const bindingPath = binding?.path;
1425
1496
  if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1426
1497
  const init = bindingPath.node.init;
1427
- return !!(init && isStyleArrayLikeExpression(init, bindingPath, seen));
1498
+ return !!(init && isStyleArrayLike(init, bindingPath, seen));
1428
1499
  }
1429
1500
  if (t3.isMemberExpression(expr) && !expr.computed && t3.isIdentifier(expr.property)) {
1430
1501
  const object = expr.object;
@@ -1439,13 +1510,13 @@ function isStyleArrayLikeExpression(expr, path, seen) {
1439
1510
  if (!t3.isObjectProperty(prop) || prop.computed) continue;
1440
1511
  if (!isMatchingPropertyName(prop.key, propertyName)) continue;
1441
1512
  const value = prop.value;
1442
- return t3.isExpression(value) && isStyleArrayLikeExpression(value, bindingPath, seen);
1513
+ return t3.isExpression(value) && isStyleArrayLike(value, bindingPath, seen);
1443
1514
  }
1444
1515
  }
1445
1516
  return false;
1446
1517
  }
1447
1518
  function isStyleArrayLikeBranch(expr, path, seen) {
1448
- return isEmptyObjectExpression(expr) || isStyleArrayLikeExpression(expr, path, seen);
1519
+ return isEmptyObjectExpression(expr) || isStyleArrayLike(expr, path, seen);
1449
1520
  }
1450
1521
  function isMatchingPropertyName(key, name) {
1451
1522
  return t3.isIdentifier(key) && key.name === name || t3.isStringLiteral(key) && key.value === name;
@@ -1493,6 +1564,8 @@ function transformTruss(code, filename, mapping) {
1493
1564
  const stylexNamespaceName = existingStylexNamespace ?? reservePreferredName(usedTopLevelNames, "stylex");
1494
1565
  const createVarName = reservePreferredName(usedTopLevelNames, "css", "css_");
1495
1566
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
1567
+ const mergePropsHelperName = reservePreferredName(usedTopLevelNames, "__mergeProps");
1568
+ const needsMergePropsHelper = { current: false };
1496
1569
  const runtimeLookupNames = /* @__PURE__ */ new Map();
1497
1570
  for (const [lookupKey] of runtimeLookups) {
1498
1571
  runtimeLookupNames.set(lookupKey, reservePreferredName(usedTopLevelNames, `__${lookupKey}`));
@@ -1504,6 +1577,8 @@ function transformTruss(code, filename, mapping) {
1504
1577
  createVarName,
1505
1578
  stylexNamespaceName,
1506
1579
  maybeIncHelperName,
1580
+ mergePropsHelperName,
1581
+ needsMergePropsHelper,
1507
1582
  runtimeLookupNames
1508
1583
  });
1509
1584
  removeCssImport(ast, cssBindingName);
@@ -1516,6 +1591,9 @@ function transformTruss(code, filename, mapping) {
1516
1591
  if (maybeIncHelperName) {
1517
1592
  declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
1518
1593
  }
1594
+ if (needsMergePropsHelper.current) {
1595
+ declarationsToInsert.push(buildMergePropsDeclaration(mergePropsHelperName, stylexNamespaceName));
1596
+ }
1519
1597
  declarationsToInsert.push(...hoistedMarkerDecls);
1520
1598
  if (createProperties.length > 0) {
1521
1599
  declarationsToInsert.push(buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties));
@@ -1587,7 +1665,72 @@ function hoistMarkerDeclarations(ast, names) {
1587
1665
 
1588
1666
  // src/plugin/transform-css.ts
1589
1667
  import { parse as parse2 } from "@babel/parser";
1668
+ import * as t6 from "@babel/types";
1669
+
1670
+ // src/plugin/css-ts-utils.ts
1590
1671
  import * as t5 from "@babel/types";
1672
+ function collectStaticStringBindings(ast) {
1673
+ const bindings = /* @__PURE__ */ new Map();
1674
+ let changed = true;
1675
+ while (changed) {
1676
+ changed = false;
1677
+ for (const node of ast.program.body) {
1678
+ const declaration = getTopLevelVariableDeclaration(node);
1679
+ if (!declaration) continue;
1680
+ for (const declarator of declaration.declarations) {
1681
+ if (!t5.isIdentifier(declarator.id) || !declarator.init) continue;
1682
+ if (bindings.has(declarator.id.name)) continue;
1683
+ const value = resolveStaticString(declarator.init, bindings);
1684
+ if (value === null) continue;
1685
+ bindings.set(declarator.id.name, value);
1686
+ changed = true;
1687
+ }
1688
+ }
1689
+ }
1690
+ return bindings;
1691
+ }
1692
+ function resolveStaticString(node, bindings) {
1693
+ if (!node) return null;
1694
+ if (t5.isStringLiteral(node)) return node.value;
1695
+ if (t5.isTemplateLiteral(node)) {
1696
+ let value = "";
1697
+ for (let i = 0; i < node.quasis.length; i++) {
1698
+ value += node.quasis[i].value.cooked ?? "";
1699
+ if (i >= node.expressions.length) continue;
1700
+ const expressionValue = resolveStaticString(node.expressions[i], bindings);
1701
+ if (expressionValue === null) return null;
1702
+ value += expressionValue;
1703
+ }
1704
+ return value;
1705
+ }
1706
+ if (t5.isIdentifier(node)) {
1707
+ return bindings.get(node.name) ?? null;
1708
+ }
1709
+ if (t5.isTSAsExpression(node) || t5.isTSSatisfiesExpression(node) || t5.isTSNonNullExpression(node)) {
1710
+ return resolveStaticString(node.expression, bindings);
1711
+ }
1712
+ if (t5.isParenthesizedExpression(node)) {
1713
+ return resolveStaticString(node.expression, bindings);
1714
+ }
1715
+ if (t5.isBinaryExpression(node, { operator: "+" })) {
1716
+ const left = resolveStaticString(node.left, bindings);
1717
+ const right = resolveStaticString(node.right, bindings);
1718
+ if (left === null || right === null) return null;
1719
+ return left + right;
1720
+ }
1721
+ return null;
1722
+ }
1723
+ function getTopLevelVariableDeclaration(node) {
1724
+ if (t5.isVariableDeclaration(node)) {
1725
+ return node;
1726
+ }
1727
+ if (t5.isExportNamedDeclaration(node) && node.declaration && t5.isVariableDeclaration(node.declaration)) {
1728
+ return node.declaration;
1729
+ }
1730
+ return null;
1731
+ }
1732
+
1733
+ // src/plugin/transform-css.ts
1591
1734
  function transformCssTs(code, filename, mapping) {
1592
1735
  const ast = parse2(code, {
1593
1736
  sourceType: "module",
@@ -1599,28 +1742,29 @@ function transformCssTs(code, filename, mapping) {
1599
1742
  return `/* [truss] ${filename}: no Css import found */
1600
1743
  `;
1601
1744
  }
1602
- const defaultExport = findDefaultExportObject(ast);
1603
- if (!defaultExport) {
1604
- return `/* [truss] ${filename}: expected \`export default { ... }\` with an object literal */
1745
+ const cssExport = findNamedCssExportObject(ast);
1746
+ if (!cssExport) {
1747
+ return `/* [truss] ${filename}: expected \`export const css = { ... }\` with an object literal */
1605
1748
  `;
1606
1749
  }
1607
1750
  const rules = [];
1608
- for (const prop of defaultExport.properties) {
1609
- if (t5.isSpreadElement(prop)) {
1751
+ const stringBindings = collectStaticStringBindings(ast);
1752
+ for (const prop of cssExport.properties) {
1753
+ if (t6.isSpreadElement(prop)) {
1610
1754
  rules.push(`/* [truss] unsupported: spread elements in css.ts export */`);
1611
1755
  continue;
1612
1756
  }
1613
- if (!t5.isObjectProperty(prop)) {
1757
+ if (!t6.isObjectProperty(prop)) {
1614
1758
  rules.push(`/* [truss] unsupported: non-property in css.ts export */`);
1615
1759
  continue;
1616
1760
  }
1617
- const selector = objectPropertyStringKey(prop);
1761
+ const selector = objectPropertyStringKey(prop, stringBindings);
1618
1762
  if (selector === null) {
1619
1763
  rules.push(`/* [truss] unsupported: non-string-literal key in css.ts export */`);
1620
1764
  continue;
1621
1765
  }
1622
1766
  const valueNode = prop.value;
1623
- if (!t5.isExpression(valueNode)) {
1767
+ if (!t6.isExpression(valueNode)) {
1624
1768
  rules.push(`/* [truss] unsupported: "${selector}" value is not an expression */`);
1625
1769
  continue;
1626
1770
  }
@@ -1633,23 +1777,32 @@ function transformCssTs(code, filename, mapping) {
1633
1777
  }
1634
1778
  return rules.join("\n\n") + "\n";
1635
1779
  }
1636
- function findDefaultExportObject(ast) {
1780
+ function findNamedCssExportObject(ast) {
1637
1781
  for (const node of ast.program.body) {
1638
- if (!t5.isExportDefaultDeclaration(node)) continue;
1639
- const decl = node.declaration;
1640
- if (t5.isObjectExpression(decl)) return decl;
1641
- if (t5.isTSAsExpression(decl) && t5.isObjectExpression(decl.expression)) return decl.expression;
1642
- if (t5.isTSSatisfiesExpression(decl) && t5.isObjectExpression(decl.expression)) return decl.expression;
1782
+ if (!t6.isExportNamedDeclaration(node) || !node.declaration) continue;
1783
+ if (!t6.isVariableDeclaration(node.declaration)) continue;
1784
+ for (const declarator of node.declaration.declarations) {
1785
+ if (!t6.isIdentifier(declarator.id, { name: "css" })) continue;
1786
+ const value = unwrapObjectExpression(declarator.init);
1787
+ if (value) return value;
1788
+ }
1643
1789
  }
1644
1790
  return null;
1645
1791
  }
1646
- function objectPropertyStringKey(prop) {
1647
- if (t5.isStringLiteral(prop.key)) return prop.key.value;
1648
- if (t5.isIdentifier(prop.key) && !prop.computed) return prop.key.name;
1792
+ function unwrapObjectExpression(node) {
1793
+ if (!node) return null;
1794
+ if (t6.isObjectExpression(node)) return node;
1795
+ if (t6.isTSAsExpression(node) || t6.isTSSatisfiesExpression(node)) return unwrapObjectExpression(node.expression);
1796
+ return null;
1797
+ }
1798
+ function objectPropertyStringKey(prop, stringBindings) {
1799
+ if (t6.isStringLiteral(prop.key)) return prop.key.value;
1800
+ if (t6.isIdentifier(prop.key) && !prop.computed) return prop.key.name;
1801
+ if (prop.computed) return resolveStaticString(prop.key, stringBindings);
1649
1802
  return null;
1650
1803
  }
1651
1804
  function resolveCssExpression(node, cssBindingName, mapping, filename) {
1652
- if (!t5.isMemberExpression(node) || node.computed || !t5.isIdentifier(node.property, { name: "$" })) {
1805
+ if (!t6.isMemberExpression(node) || node.computed || !t6.isIdentifier(node.property, { name: "$" })) {
1653
1806
  return { error: "value must be a Css.*.$ expression" };
1654
1807
  }
1655
1808
  const chain = extractChain(node.object, cssBindingName);
@@ -1718,8 +1871,61 @@ ${body}
1718
1871
  }`;
1719
1872
  }
1720
1873
 
1874
+ // src/plugin/rewrite-css-ts-imports.ts
1875
+ import { parse as parse3 } from "@babel/parser";
1876
+ import _generate2 from "@babel/generator";
1877
+ import * as t7 from "@babel/types";
1878
+ var generate2 = _generate2.default ?? _generate2;
1879
+ function rewriteCssTsImports(code, filename) {
1880
+ if (!code.includes(".css.ts")) {
1881
+ return { code, changed: false };
1882
+ }
1883
+ const ast = parse3(code, {
1884
+ sourceType: "module",
1885
+ plugins: ["typescript", "jsx"],
1886
+ sourceFilename: filename
1887
+ });
1888
+ const existingCssSideEffects = /* @__PURE__ */ new Set();
1889
+ const neededCssSideEffects = /* @__PURE__ */ new Set();
1890
+ let changed = false;
1891
+ for (const node of ast.program.body) {
1892
+ if (!t7.isImportDeclaration(node)) continue;
1893
+ if (typeof node.source.value !== "string") continue;
1894
+ if (!node.source.value.endsWith(".css.ts")) continue;
1895
+ if (node.specifiers.length === 0) {
1896
+ node.source = t7.stringLiteral(toVirtualCssSpecifier(node.source.value));
1897
+ existingCssSideEffects.add(node.source.value);
1898
+ changed = true;
1899
+ continue;
1900
+ }
1901
+ neededCssSideEffects.add(toVirtualCssSpecifier(node.source.value));
1902
+ }
1903
+ const sideEffectImports = [];
1904
+ for (const source of neededCssSideEffects) {
1905
+ if (existingCssSideEffects.has(source)) continue;
1906
+ sideEffectImports.push(t7.importDeclaration([], t7.stringLiteral(source)));
1907
+ changed = true;
1908
+ }
1909
+ if (!changed) {
1910
+ return { code, changed: false };
1911
+ }
1912
+ if (sideEffectImports.length > 0) {
1913
+ const insertIndex = findLastImportIndex(ast) + 1;
1914
+ ast.program.body.splice(insertIndex, 0, ...sideEffectImports);
1915
+ }
1916
+ const output = generate2(ast, {
1917
+ sourceFileName: filename,
1918
+ retainLines: false
1919
+ });
1920
+ return { code: output.code, changed: true };
1921
+ }
1922
+ function toVirtualCssSpecifier(source) {
1923
+ return `${source}?truss-css`;
1924
+ }
1925
+
1721
1926
  // src/plugin/index.ts
1722
1927
  var VIRTUAL_CSS_PREFIX = "\0truss-css:";
1928
+ var CSS_TS_QUERY = "?truss-css";
1723
1929
  function trussPlugin(opts) {
1724
1930
  let mapping = null;
1725
1931
  let projectRoot;
@@ -1743,15 +1949,8 @@ function trussPlugin(opts) {
1743
1949
  ensureMapping();
1744
1950
  },
1745
1951
  resolveId(source, importer) {
1746
- if (!source.endsWith(".css.ts")) return null;
1747
- let absolutePath;
1748
- if (isAbsolute(source)) {
1749
- absolutePath = source;
1750
- } else if (importer) {
1751
- absolutePath = resolve(dirname(importer), source);
1752
- } else {
1753
- absolutePath = resolve(projectRoot || process.cwd(), source);
1754
- }
1952
+ if (!source.endsWith(CSS_TS_QUERY)) return null;
1953
+ const absolutePath = resolveImportPath(source.slice(0, -CSS_TS_QUERY.length), importer, projectRoot);
1755
1954
  if (!existsSync(absolutePath)) return null;
1756
1955
  return VIRTUAL_CSS_PREFIX + absolutePath.slice(0, -3);
1757
1956
  },
@@ -1763,17 +1962,38 @@ function trussPlugin(opts) {
1763
1962
  },
1764
1963
  transform(code, id) {
1765
1964
  if (!/\.[cm]?[jt]sx?(\?|$)/.test(id)) return null;
1766
- if (!code.includes("Css")) return null;
1965
+ const rewrittenImports = rewriteCssTsImports(code, id);
1966
+ const rewrittenCode = rewrittenImports.code;
1967
+ const hasCssDsl = rewrittenCode.includes("Css");
1968
+ if (!hasCssDsl && !rewrittenImports.changed) return null;
1767
1969
  const fileId = stripQueryAndHash(id);
1768
1970
  if (isNodeModulesFile(fileId) && !isWhitelistedExternalPackageFile(fileId, externalPackages)) {
1769
1971
  return null;
1770
1972
  }
1771
- const result = transformTruss(code, id, ensureMapping());
1772
- if (!result) return null;
1973
+ if (fileId.endsWith(".css.ts")) {
1974
+ return rewrittenImports.changed ? { code: rewrittenCode, map: null } : null;
1975
+ }
1976
+ if (!hasCssDsl) {
1977
+ return { code: rewrittenCode, map: null };
1978
+ }
1979
+ const result = transformTruss(rewrittenCode, id, ensureMapping());
1980
+ if (!result) {
1981
+ if (!rewrittenImports.changed) return null;
1982
+ return { code: rewrittenCode, map: null };
1983
+ }
1773
1984
  return { code: result.code, map: result.map };
1774
1985
  }
1775
1986
  };
1776
1987
  }
1988
+ function resolveImportPath(source, importer, projectRoot) {
1989
+ if (isAbsolute(source)) {
1990
+ return source;
1991
+ }
1992
+ if (importer) {
1993
+ return resolve(dirname(importer), source);
1994
+ }
1995
+ return resolve(projectRoot || process.cwd(), source);
1996
+ }
1777
1997
  function stripQueryAndHash(id) {
1778
1998
  const queryIndex = id.indexOf("?");
1779
1999
  const hashIndex = id.indexOf("#");