@homebound/truss 2.0.13 → 2.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,28 @@
1
- import * as stylex from '@stylexjs/stylex';
2
-
1
+ /** A compact source label for a Truss CSS expression, used in debug mode. */
3
2
  declare class TrussDebugInfo {
4
- /** A compact `FileName.tsx:line` source label for a Truss CSS expression. */
3
+ /** I.e. `"FileName.tsx:line"` */
5
4
  readonly src: string;
6
5
  constructor(src: string);
7
6
  }
8
- /** Call StyleX while stripping Truss debug sentinels from the style list. */
9
- declare function trussProps(stylexNs: typeof stylex, ...styles: unknown[]): Record<string, unknown>;
10
- declare function mergeProps(stylexNs: typeof stylex, explicitClassName: string, ...styles: unknown[]): Record<string, unknown>;
11
7
  /**
12
- * Coerce maybe-array StyleX inputs into arrays, guarding against nullish/false and plain object values.
8
+ * Space-separated atomic class names, or a variable tuple with class names + CSS variable map.
9
+ *
10
+ * In debug mode, the transform appends a TrussDebugInfo as an extra tuple element:
11
+ * - static with debug: `[classNames, debugInfo]`
12
+ * - variable with debug: `[classNames, vars, debugInfo]`
13
+ */
14
+ type TrussStyleValue = string | [classNames: string, vars: Record<string, string>] | [classNames: string, debugInfo: TrussDebugInfo] | [classNames: string, vars: Record<string, string>, debugInfo: TrussDebugInfo];
15
+ /** A property-keyed style hash where each key owns one logical CSS property. */
16
+ type TrussStyleHash = Record<string, TrussStyleValue>;
17
+ /** Merge one or more Truss style hashes into `{ className, style?, data-truss-src? }`. */
18
+ declare function trussProps(...hashes: ReadonlyArray<TrussStyleHash | false | null | undefined>): Record<string, unknown>;
19
+ /** Merge explicit className/style with Truss style hashes. */
20
+ declare function mergeProps(explicitClassName: string | undefined, explicitStyle: Record<string, unknown> | undefined, ...hashes: ReadonlyArray<TrussStyleHash | false | null | undefined>): Record<string, unknown>;
21
+ /**
22
+ * Inject CSS text into the document for jsdom/test environments.
13
23
  *
14
- * I.e. `...asStyleArray(xss)` stays safe when destructured `xss` is `undefined`.
24
+ * In browser dev mode, CSS is served via the Vite virtual endpoint instead.
15
25
  */
16
- declare function asStyleArray(styles: unknown): ReadonlyArray<unknown>;
26
+ declare function __injectTrussCSS(cssText: string): void;
17
27
 
18
- export { TrussDebugInfo, asStyleArray, mergeProps, trussProps };
28
+ export { TrussDebugInfo, type TrussStyleHash, type TrussStyleValue, __injectTrussCSS, mergeProps, trussProps };
package/build/runtime.js CHANGED
@@ -1,60 +1,76 @@
1
1
  // src/runtime.ts
2
2
  var TrussDebugInfo = class {
3
- /** A compact `FileName.tsx:line` source label for a Truss CSS expression. */
3
+ /** I.e. `"FileName.tsx:line"` */
4
4
  src;
5
5
  constructor(src) {
6
6
  this.src = src;
7
7
  }
8
8
  };
9
- function trussProps(stylexNs, ...styles) {
10
- const { debugSources, styleArgs } = splitDebugInfo(styles);
11
- const sx = stylexNs.props(...styleArgs);
12
- return applyDebugSources(sx, debugSources);
13
- }
14
- function mergeProps(stylexNs, explicitClassName, ...styles) {
15
- const { debugSources, styleArgs } = splitDebugInfo(styles);
16
- const sx = stylexNs.props(...styleArgs);
17
- return {
18
- ...applyDebugSources(sx, debugSources),
19
- className: `${explicitClassName} ${sx.className ?? ""}`.trim()
9
+ function trussProps(...hashes) {
10
+ const merged = {};
11
+ for (const hash of hashes) {
12
+ if (!hash || typeof hash !== "object") continue;
13
+ Object.assign(merged, hash);
14
+ }
15
+ const classNames = [];
16
+ const inlineStyle = {};
17
+ const debugSources = [];
18
+ for (const [key, value] of Object.entries(merged)) {
19
+ if (key === "__marker") {
20
+ if (typeof value === "string") classNames.push(value);
21
+ continue;
22
+ }
23
+ if (typeof value === "string") {
24
+ classNames.push(value);
25
+ continue;
26
+ }
27
+ classNames.push(value[0]);
28
+ for (let i = 1; i < value.length; i++) {
29
+ const el = value[i];
30
+ if (el instanceof TrussDebugInfo) {
31
+ debugSources.push(el.src);
32
+ } else if (typeof el === "object" && el !== null) {
33
+ Object.assign(inlineStyle, el);
34
+ }
35
+ }
36
+ }
37
+ const props = {
38
+ className: classNames.join(" ")
20
39
  };
21
- }
22
- function asStyleArray(styles) {
23
- if (Array.isArray(styles)) {
24
- return styles;
40
+ if (Object.keys(inlineStyle).length > 0) {
41
+ props.style = inlineStyle;
25
42
  }
26
- if (styles && typeof styles === "object" && Object.keys(styles).length > 0) {
27
- console.error(
28
- "[truss] asStyleArray received a non-empty object \u2014 this likely means a style value was not rewritten to an array. Use a style array (e.g. Css.df.$) or an empty array [] instead of {}."
29
- );
43
+ if (debugSources.length > 0) {
44
+ props["data-truss-src"] = [...new Set(debugSources)].join("; ");
30
45
  }
31
- return styles ? [styles] : [];
46
+ return props;
32
47
  }
33
- function splitDebugInfo(styles) {
34
- const debugSources = [];
35
- const styleArgs = [];
36
- for (const style of styles) {
37
- if (style instanceof TrussDebugInfo) {
38
- debugSources.push(style.src);
39
- } else {
40
- styleArgs.push(style);
41
- }
48
+ function mergeProps(explicitClassName, explicitStyle, ...hashes) {
49
+ const result = trussProps(...hashes);
50
+ if (explicitClassName) {
51
+ result.className = `${explicitClassName} ${result.className ?? ""}`.trim();
52
+ }
53
+ if (explicitStyle) {
54
+ result.style = { ...explicitStyle, ...result.style };
42
55
  }
43
- return { debugSources, styleArgs };
56
+ return result;
44
57
  }
45
- function applyDebugSources(props, debugSources) {
46
- if (debugSources.length === 0) {
47
- return props;
48
- }
49
- const uniqueSources = Array.from(new Set(debugSources));
50
- return {
51
- ...props,
52
- "data-truss-src": uniqueSources.join("; ")
53
- };
58
+ function __injectTrussCSS(cssText) {
59
+ if (typeof document === "undefined") return;
60
+ const id = "data-truss";
61
+ let style = document.querySelector(`style[${id}]`);
62
+ if (!style) {
63
+ style = document.createElement("style");
64
+ style.setAttribute(id, "");
65
+ document.head.appendChild(style);
66
+ }
67
+ if (!style.textContent?.includes(cssText)) {
68
+ style.textContent = (style.textContent ?? "") + cssText;
69
+ }
54
70
  }
55
71
  export {
56
72
  TrussDebugInfo,
57
- asStyleArray,
73
+ __injectTrussCSS,
58
74
  mergeProps,
59
75
  trussProps
60
76
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["import type * as stylex from \"@stylexjs/stylex\";\n\nexport class TrussDebugInfo {\n /** A compact `FileName.tsx:line` source label for a Truss CSS expression. */\n readonly src: string;\n\n constructor(src: string) {\n this.src = src;\n }\n}\n\ntype StylexPropArg = Parameters<typeof stylex.props>[number];\n\n/** Call StyleX while stripping Truss debug sentinels from the style list. */\nexport function trussProps(stylexNs: typeof stylex, ...styles: unknown[]): Record<string, unknown> {\n const { debugSources, styleArgs } = splitDebugInfo(styles);\n const sx = stylexNs.props(...styleArgs);\n return applyDebugSources(sx, debugSources);\n}\n\nexport function mergeProps(\n stylexNs: typeof stylex,\n explicitClassName: string,\n ...styles: unknown[]\n): Record<string, unknown> {\n const { debugSources, styleArgs } = splitDebugInfo(styles);\n const sx = stylexNs.props(...styleArgs);\n return {\n ...applyDebugSources(sx, debugSources),\n className: `${explicitClassName} ${sx.className ?? \"\"}`.trim(),\n };\n}\n\n/**\n * Coerce maybe-array StyleX inputs into arrays, guarding against nullish/false and plain object values.\n *\n * I.e. `...asStyleArray(xss)` stays safe when destructured `xss` is `undefined`.\n */\nexport function asStyleArray(styles: unknown): ReadonlyArray<unknown> {\n if (Array.isArray(styles)) {\n return styles;\n }\n if (styles && typeof styles === \"object\" && Object.keys(styles).length > 0) {\n console.error(\n \"[truss] asStyleArray received a non-empty object this likely means a style value was not rewritten to an array. \" +\n \"Use a style array (e.g. Css.df.$) or an empty array [] instead of {}.\",\n );\n }\n return styles ? [styles] : [];\n}\n\n/** Collect Truss debug info while preserving the original StyleX argument order. */\nfunction splitDebugInfo(styles: ReadonlyArray<unknown>): {\n debugSources: string[];\n styleArgs: StylexPropArg[];\n} {\n const debugSources: string[] = [];\n const styleArgs: StylexPropArg[] = [];\n\n for (const style of styles) {\n if (style instanceof TrussDebugInfo) {\n debugSources.push(style.src);\n } else {\n styleArgs.push(style as StylexPropArg);\n }\n }\n\n return { debugSources, styleArgs };\n}\n\n/** Deduplicate and attach compact Truss source labels to emitted props. */\nfunction applyDebugSources(\n props: Record<string, unknown>,\n debugSources: ReadonlyArray<string>,\n): Record<string, unknown> {\n if (debugSources.length === 0) {\n return props;\n }\n\n const uniqueSources = Array.from(new Set(debugSources));\n return {\n ...props,\n \"data-truss-src\": uniqueSources.join(\"; \"),\n };\n}\n"],"mappings":";AAEO,IAAM,iBAAN,MAAqB;AAAA;AAAA,EAEjB;AAAA,EAET,YAAY,KAAa;AACvB,SAAK,MAAM;AAAA,EACb;AACF;AAKO,SAAS,WAAW,aAA4B,QAA4C;AACjG,QAAM,EAAE,cAAc,UAAU,IAAI,eAAe,MAAM;AACzD,QAAM,KAAK,SAAS,MAAM,GAAG,SAAS;AACtC,SAAO,kBAAkB,IAAI,YAAY;AAC3C;AAEO,SAAS,WACd,UACA,sBACG,QACsB;AACzB,QAAM,EAAE,cAAc,UAAU,IAAI,eAAe,MAAM;AACzD,QAAM,KAAK,SAAS,MAAM,GAAG,SAAS;AACtC,SAAO;AAAA,IACL,GAAG,kBAAkB,IAAI,YAAY;AAAA,IACrC,WAAW,GAAG,iBAAiB,IAAI,GAAG,aAAa,EAAE,GAAG,KAAK;AAAA,EAC/D;AACF;AAOO,SAAS,aAAa,QAAyC;AACpE,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO;AAAA,EACT;AACA,MAAI,UAAU,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAC1E,YAAQ;AAAA,MACN;AAAA,IAEF;AAAA,EACF;AACA,SAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAC9B;AAGA,SAAS,eAAe,QAGtB;AACA,QAAM,eAAyB,CAAC;AAChC,QAAM,YAA6B,CAAC;AAEpC,aAAW,SAAS,QAAQ;AAC1B,QAAI,iBAAiB,gBAAgB;AACnC,mBAAa,KAAK,MAAM,GAAG;AAAA,IAC7B,OAAO;AACL,gBAAU,KAAK,KAAsB;AAAA,IACvC;AAAA,EACF;AAEA,SAAO,EAAE,cAAc,UAAU;AACnC;AAGA,SAAS,kBACP,OACA,cACyB;AACzB,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,MAAM,KAAK,IAAI,IAAI,YAAY,CAAC;AACtD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,kBAAkB,cAAc,KAAK,IAAI;AAAA,EAC3C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/** A compact source label for a Truss CSS expression, used in debug mode. */\nexport class TrussDebugInfo {\n /** I.e. `\"FileName.tsx:line\"` */\n readonly src: string;\n\n constructor(src: string) {\n this.src = src;\n }\n}\n\n/**\n * Space-separated atomic class names, or a variable tuple with class names + CSS variable map.\n *\n * In debug mode, the transform appends a TrussDebugInfo as an extra tuple element:\n * - static with debug: `[classNames, debugInfo]`\n * - variable with debug: `[classNames, vars, debugInfo]`\n */\nexport type TrussStyleValue =\n | string\n | [classNames: string, vars: Record<string, string>]\n | [classNames: string, debugInfo: TrussDebugInfo]\n | [classNames: string, vars: Record<string, string>, debugInfo: TrussDebugInfo];\n\n/** A property-keyed style hash where each key owns one logical CSS property. */\nexport type TrussStyleHash = Record<string, TrussStyleValue>;\n\n/** Merge one or more Truss style hashes into `{ className, style?, data-truss-src? }`. */\nexport function trussProps(\n ...hashes: ReadonlyArray<TrussStyleHash | false | null | undefined>\n): Record<string, unknown> {\n const merged: Record<string, TrussStyleValue> = {};\n\n for (const hash of hashes) {\n if (!hash || typeof hash !== \"object\") continue;\n Object.assign(merged, hash);\n }\n\n const classNames: string[] = [];\n const inlineStyle: Record<string, string> = {};\n const debugSources: string[] = [];\n\n for (const [key, value] of Object.entries(merged)) {\n // __marker is a special key — its value is a marker class name, not a CSS property\n if (key === \"__marker\") {\n if (typeof value === \"string\") classNames.push(value);\n continue;\n }\n if (typeof value === \"string\") {\n // I.e. \"df\" or \"black blue_h\"\n classNames.push(value);\n continue;\n }\n\n // Tuple: [classNames, varsOrDebug?, maybeDebug?]\n classNames.push(value[0]);\n\n for (let i = 1; i < value.length; i++) {\n const el = value[i];\n if (el instanceof TrussDebugInfo) {\n debugSources.push(el.src);\n } else if (typeof el === \"object\" && el !== null) {\n Object.assign(inlineStyle, el);\n }\n }\n }\n\n const props: Record<string, unknown> = {\n className: classNames.join(\" \"),\n };\n\n if (Object.keys(inlineStyle).length > 0) {\n props.style = inlineStyle;\n }\n\n if (debugSources.length > 0) {\n props[\"data-truss-src\"] = [...new Set(debugSources)].join(\"; \");\n }\n\n return props;\n}\n\n/** Merge explicit className/style with Truss style hashes. */\nexport function mergeProps(\n explicitClassName: string | undefined,\n explicitStyle: Record<string, unknown> | undefined,\n ...hashes: ReadonlyArray<TrussStyleHash | false | null | undefined>\n): Record<string, unknown> {\n const result = trussProps(...hashes);\n\n if (explicitClassName) {\n result.className = `${explicitClassName} ${result.className ?? \"\"}`.trim();\n }\n\n if (explicitStyle) {\n result.style = { ...explicitStyle, ...(result.style as Record<string, unknown> | undefined) };\n }\n\n return result;\n}\n\n/**\n * Inject CSS text into the document for jsdom/test environments.\n *\n * In browser dev mode, CSS is served via the Vite virtual endpoint instead.\n */\nexport function __injectTrussCSS(cssText: string): void {\n if (typeof document === \"undefined\") return;\n\n const id = \"data-truss\";\n let style = document.querySelector(`style[${id}]`) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement(\"style\");\n style.setAttribute(id, \"\");\n document.head.appendChild(style);\n }\n\n // Append if not already present (dedupe across HMR re-executions)\n if (!style.textContent?.includes(cssText)) {\n style.textContent = (style.textContent ?? \"\") + cssText;\n }\n}\n"],"mappings":";AACO,IAAM,iBAAN,MAAqB;AAAA;AAAA,EAEjB;AAAA,EAET,YAAY,KAAa;AACvB,SAAK,MAAM;AAAA,EACb;AACF;AAmBO,SAAS,cACX,QACsB;AACzB,QAAM,SAA0C,CAAC;AAEjD,aAAW,QAAQ,QAAQ;AACzB,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,WAAO,OAAO,QAAQ,IAAI;AAAA,EAC5B;AAEA,QAAM,aAAuB,CAAC;AAC9B,QAAM,cAAsC,CAAC;AAC7C,QAAM,eAAyB,CAAC;AAEhC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAEjD,QAAI,QAAQ,YAAY;AACtB,UAAI,OAAO,UAAU,SAAU,YAAW,KAAK,KAAK;AACpD;AAAA,IACF;AACA,QAAI,OAAO,UAAU,UAAU;AAE7B,iBAAW,KAAK,KAAK;AACrB;AAAA,IACF;AAGA,eAAW,KAAK,MAAM,CAAC,CAAC;AAExB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,KAAK,MAAM,CAAC;AAClB,UAAI,cAAc,gBAAgB;AAChC,qBAAa,KAAK,GAAG,GAAG;AAAA,MAC1B,WAAW,OAAO,OAAO,YAAY,OAAO,MAAM;AAChD,eAAO,OAAO,aAAa,EAAE;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAiC;AAAA,IACrC,WAAW,WAAW,KAAK,GAAG;AAAA,EAChC;AAEA,MAAI,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG;AACvC,UAAM,QAAQ;AAAA,EAChB;AAEA,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,gBAAgB,IAAI,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC,EAAE,KAAK,IAAI;AAAA,EAChE;AAEA,SAAO;AACT;AAGO,SAAS,WACd,mBACA,kBACG,QACsB;AACzB,QAAM,SAAS,WAAW,GAAG,MAAM;AAEnC,MAAI,mBAAmB;AACrB,WAAO,YAAY,GAAG,iBAAiB,IAAI,OAAO,aAAa,EAAE,GAAG,KAAK;AAAA,EAC3E;AAEA,MAAI,eAAe;AACjB,WAAO,QAAQ,EAAE,GAAG,eAAe,GAAI,OAAO,MAA8C;AAAA,EAC9F;AAEA,SAAO;AACT;AAOO,SAAS,iBAAiB,SAAuB;AACtD,MAAI,OAAO,aAAa,YAAa;AAErC,QAAM,KAAK;AACX,MAAI,QAAQ,SAAS,cAAc,SAAS,EAAE,GAAG;AACjD,MAAI,CAAC,OAAO;AACV,YAAQ,SAAS,cAAc,OAAO;AACtC,UAAM,aAAa,IAAI,EAAE;AACzB,aAAS,KAAK,YAAY,KAAK;AAAA,EACjC;AAGA,MAAI,CAAC,MAAM,aAAa,SAAS,OAAO,GAAG;AACzC,UAAM,eAAe,MAAM,eAAe,MAAM;AAAA,EAClD;AACF;","names":[]}
package/cli.js CHANGED
@@ -1,16 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { join } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { register } from "tsx/esm/api";
6
- import { generate } from "./build/index.js";
7
-
8
- // Register tsx loader so we can import TypeScript config files
9
- // (handles both CJS and ESM projects, with extensionless .ts imports).
10
- const unregister = register();
3
+ import { createJiti } from "jiti";
4
+ import { join } from "path";
11
5
 
12
6
  const filename = process.argv[2] ?? "./truss-config.ts";
13
7
  const configPath = join(process.cwd(), filename);
8
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
14
9
 
15
10
  main().catch((err) => {
16
11
  console.error(err);
@@ -18,8 +13,8 @@ main().catch((err) => {
18
13
  });
19
14
 
20
15
  async function main() {
21
- const config = (await import(pathToFileURL(configPath).href)).default;
22
- unregister();
16
+ const config = await jiti.import(configPath, { default: true });
17
+ const { generate } = await import("./build/index.js");
23
18
  await generate(config);
24
19
  console.log(`Generated ${config.outputPath}`);
25
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/truss",
3
- "version": "2.0.13",
3
+ "version": "2.1.0-next.1",
4
4
  "type": "module",
5
5
  "main": "build/index.js",
6
6
  "bin": "cli.js",
@@ -27,10 +27,14 @@
27
27
  },
28
28
  "license": "ISC",
29
29
  "dependencies": {
30
+ "@babel/generator": "^7.29.0",
31
+ "@babel/parser": "^7.29.0",
32
+ "@babel/traverse": "^7.29.0",
33
+ "@babel/types": "^7.29.0",
30
34
  "change-case": "^5.4.4",
31
35
  "csstype": "^3.2.3",
32
- "ts-poet": "^6.12.0",
33
- "tsx": "^4.19.0"
36
+ "jiti": "^2.6.1",
37
+ "ts-poet": "^6.12.0"
34
38
  },
35
39
  "devDependencies": {
36
40
  "@homebound/tsconfig": "^1.1.1",