@homebound/truss 2.14.0 → 2.15.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.
package/build/plugin/index.d.ts
CHANGED
|
@@ -41,7 +41,8 @@ interface TrussEsbuildPluginOptions {
|
|
|
41
41
|
outputCss?: string;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* esbuild plugin that transforms `Css.*.$` expressions
|
|
44
|
+
* esbuild plugin that transforms `Css.*.$` expressions, collects `.css.ts` blocks,
|
|
45
|
+
* and emits a `truss.css` file.
|
|
45
46
|
*
|
|
46
47
|
* Designed for library builds using tsup/esbuild. Transforms source files
|
|
47
48
|
* during the build and writes an annotated `truss.css` alongside the output
|
|
@@ -101,6 +102,7 @@ interface TrussVitePlugin {
|
|
|
101
102
|
transformIndexHtml?: (html: string) => string;
|
|
102
103
|
handleHotUpdate?: (ctx: any) => void;
|
|
103
104
|
generateBundle?: (options: any, bundle: any) => void;
|
|
105
|
+
writeBundle?: (options: any, bundle: any) => void;
|
|
104
106
|
}
|
|
105
107
|
/**
|
|
106
108
|
* Vite plugin that transforms `Css.*.$` expressions from truss's CssBuilder DSL
|
|
@@ -111,7 +113,7 @@ interface TrussVitePlugin {
|
|
|
111
113
|
* while imports are supplemented with a virtual CSS side-effect module.
|
|
112
114
|
*
|
|
113
115
|
* In dev mode, serves CSS via a virtual endpoint that the injected runtime keeps in sync.
|
|
114
|
-
* In production, emits a
|
|
116
|
+
* In production, emits a content-hashed CSS asset (e.g. `assets/truss-abc123.css`) for long-term caching.
|
|
115
117
|
*/
|
|
116
118
|
declare function trussPlugin(opts: TrussPluginOptions): TrussVitePlugin;
|
|
117
119
|
/** Load a truss mapping file synchronously (for tests). */
|
package/build/plugin/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/plugin/index.ts
|
|
2
|
-
import { readFileSync as readFileSync3, existsSync } from "fs";
|
|
3
|
-
import { resolve as resolve2, dirname, isAbsolute } from "path";
|
|
2
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync, readdirSync } from "fs";
|
|
3
|
+
import { resolve as resolve2, dirname, isAbsolute, join as join2 } from "path";
|
|
4
|
+
import { createHash } from "crypto";
|
|
4
5
|
|
|
5
6
|
// src/plugin/emit-truss.ts
|
|
6
7
|
import * as t from "@babel/types";
|
|
@@ -3357,11 +3358,14 @@ function toVirtualCssSpecifier(source) {
|
|
|
3357
3358
|
import { readFileSync } from "fs";
|
|
3358
3359
|
var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
|
|
3359
3360
|
var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
|
|
3361
|
+
var ARBITRARY_START_RE = /^\/\* @truss arbitrary:start \*\/$/;
|
|
3362
|
+
var ARBITRARY_END_RE = /^\/\* @truss arbitrary:end \*\/$/;
|
|
3360
3363
|
var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
|
|
3361
3364
|
function parseTrussCss(cssText) {
|
|
3362
3365
|
const lines = cssText.split("\n");
|
|
3363
3366
|
const rules = [];
|
|
3364
3367
|
const properties = [];
|
|
3368
|
+
const arbitraryCssBlocks = [];
|
|
3365
3369
|
let i = 0;
|
|
3366
3370
|
while (i < lines.length) {
|
|
3367
3371
|
const line = lines[i].trim();
|
|
@@ -3390,19 +3394,43 @@ function parseTrussCss(cssText) {
|
|
|
3390
3394
|
i++;
|
|
3391
3395
|
continue;
|
|
3392
3396
|
}
|
|
3397
|
+
if (ARBITRARY_START_RE.test(line)) {
|
|
3398
|
+
i++;
|
|
3399
|
+
const blockLines = [];
|
|
3400
|
+
while (i < lines.length && !ARBITRARY_END_RE.test(lines[i].trim())) {
|
|
3401
|
+
blockLines.push(lines[i]);
|
|
3402
|
+
i++;
|
|
3403
|
+
}
|
|
3404
|
+
const blockText = blockLines.join("\n").trim();
|
|
3405
|
+
if (blockText.length > 0) {
|
|
3406
|
+
arbitraryCssBlocks.push({ cssText: blockText });
|
|
3407
|
+
}
|
|
3408
|
+
if (i < lines.length && ARBITRARY_END_RE.test(lines[i].trim())) {
|
|
3409
|
+
i++;
|
|
3410
|
+
}
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3393
3413
|
i++;
|
|
3394
3414
|
}
|
|
3395
|
-
return { rules, properties };
|
|
3415
|
+
return { rules, properties, arbitraryCssBlocks };
|
|
3396
3416
|
}
|
|
3397
3417
|
function readTrussCss(filePath) {
|
|
3398
3418
|
const content = readFileSync(filePath, "utf8");
|
|
3399
3419
|
return parseTrussCss(content);
|
|
3400
3420
|
}
|
|
3421
|
+
function annotateArbitraryCssBlock(cssText) {
|
|
3422
|
+
const trimmed = cssText.trim();
|
|
3423
|
+
if (trimmed.length === 0) {
|
|
3424
|
+
return "";
|
|
3425
|
+
}
|
|
3426
|
+
return ["/* @truss arbitrary:start */", trimmed, "/* @truss arbitrary:end */"].join("\n");
|
|
3427
|
+
}
|
|
3401
3428
|
function mergeTrussCss(sources) {
|
|
3402
3429
|
const seenClasses = /* @__PURE__ */ new Set();
|
|
3403
3430
|
const allRules = [];
|
|
3404
3431
|
const seenProperties = /* @__PURE__ */ new Set();
|
|
3405
3432
|
const allProperties = [];
|
|
3433
|
+
const allArbitraryCssBlocks = [];
|
|
3406
3434
|
for (const source of sources) {
|
|
3407
3435
|
for (const rule of source.rules) {
|
|
3408
3436
|
if (!seenClasses.has(rule.className)) {
|
|
@@ -3416,6 +3444,7 @@ function mergeTrussCss(sources) {
|
|
|
3416
3444
|
allProperties.push(prop);
|
|
3417
3445
|
}
|
|
3418
3446
|
}
|
|
3447
|
+
allArbitraryCssBlocks.push(...source.arbitraryCssBlocks ?? []);
|
|
3419
3448
|
}
|
|
3420
3449
|
allRules.sort((a, b) => {
|
|
3421
3450
|
const diff = a.priority - b.priority;
|
|
@@ -3431,6 +3460,9 @@ function mergeTrussCss(sources) {
|
|
|
3431
3460
|
lines.push(`/* @truss @property */`);
|
|
3432
3461
|
lines.push(prop.cssText);
|
|
3433
3462
|
}
|
|
3463
|
+
for (const block of allArbitraryCssBlocks) {
|
|
3464
|
+
lines.push(annotateArbitraryCssBlock(block.cssText));
|
|
3465
|
+
}
|
|
3434
3466
|
return lines.join("\n");
|
|
3435
3467
|
}
|
|
3436
3468
|
|
|
@@ -3439,6 +3471,7 @@ import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
|
3439
3471
|
import { resolve, join } from "path";
|
|
3440
3472
|
function trussEsbuildPlugin(opts) {
|
|
3441
3473
|
const cssRegistry = /* @__PURE__ */ new Map();
|
|
3474
|
+
const arbitraryCssRegistry = /* @__PURE__ */ new Map();
|
|
3442
3475
|
let mapping = null;
|
|
3443
3476
|
let outDir;
|
|
3444
3477
|
return {
|
|
@@ -3447,6 +3480,18 @@ function trussEsbuildPlugin(opts) {
|
|
|
3447
3480
|
outDir = build.initialOptions.outdir ?? build.initialOptions.outdir;
|
|
3448
3481
|
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, (args) => {
|
|
3449
3482
|
const code = readFileSync2(args.path, "utf8");
|
|
3483
|
+
if (args.path.endsWith(".css.ts")) {
|
|
3484
|
+
if (!mapping) {
|
|
3485
|
+
mapping = loadMapping(resolve(process.cwd(), opts.mapping));
|
|
3486
|
+
}
|
|
3487
|
+
const css = annotateArbitraryCssBlock(transformCssTs(code, args.path, mapping));
|
|
3488
|
+
if (css.length > 0) {
|
|
3489
|
+
arbitraryCssRegistry.set(args.path, css);
|
|
3490
|
+
} else {
|
|
3491
|
+
arbitraryCssRegistry.delete(args.path);
|
|
3492
|
+
}
|
|
3493
|
+
return { contents: code, loader: loaderForPath(args.path) };
|
|
3494
|
+
}
|
|
3450
3495
|
if (!code.includes("Css") && !code.includes("css=")) return void 0;
|
|
3451
3496
|
if (!mapping) {
|
|
3452
3497
|
mapping = loadMapping(resolve(process.cwd(), opts.mapping));
|
|
@@ -3463,8 +3508,11 @@ function trussEsbuildPlugin(opts) {
|
|
|
3463
3508
|
return { contents: result.code, loader: loaderForPath(args.path) };
|
|
3464
3509
|
});
|
|
3465
3510
|
build.onEnd(() => {
|
|
3466
|
-
if (cssRegistry.size === 0) return;
|
|
3467
|
-
const
|
|
3511
|
+
if (cssRegistry.size === 0 && arbitraryCssRegistry.size === 0) return;
|
|
3512
|
+
const cssParts = [generateCssText(cssRegistry), ...arbitraryCssRegistry.values()].filter(
|
|
3513
|
+
(part) => part.length > 0
|
|
3514
|
+
);
|
|
3515
|
+
const css = cssParts.join("\n");
|
|
3468
3516
|
const cssFileName = opts.outputCss ?? "truss.css";
|
|
3469
3517
|
const cssPath = resolve(outDir ?? join(process.cwd(), "dist"), cssFileName);
|
|
3470
3518
|
mkdirSync(resolve(cssPath, ".."), { recursive: true });
|
|
@@ -3483,6 +3531,7 @@ function loaderForPath(filePath) {
|
|
|
3483
3531
|
// src/plugin/index.ts
|
|
3484
3532
|
var VIRTUAL_CSS_PREFIX = "\0truss-css:";
|
|
3485
3533
|
var CSS_TS_QUERY = "?truss-css";
|
|
3534
|
+
var TRUSS_CSS_PLACEHOLDER = "__TRUSS_CSS_HASH__";
|
|
3486
3535
|
var VIRTUAL_CSS_ENDPOINT = "/virtual:truss.css";
|
|
3487
3536
|
var VIRTUAL_RUNTIME_ID = "virtual:truss:runtime";
|
|
3488
3537
|
var RESOLVED_VIRTUAL_RUNTIME_ID = "\0" + VIRTUAL_RUNTIME_ID;
|
|
@@ -3495,6 +3544,7 @@ function trussPlugin(opts) {
|
|
|
3495
3544
|
let isTest = false;
|
|
3496
3545
|
let isBuild = false;
|
|
3497
3546
|
const libraryPaths = opts.libraries ?? [];
|
|
3547
|
+
let emittedCssFileName = null;
|
|
3498
3548
|
const cssRegistry = /* @__PURE__ */ new Map();
|
|
3499
3549
|
let cssVersion = 0;
|
|
3500
3550
|
let lastSentVersion = 0;
|
|
@@ -3562,8 +3612,9 @@ function trussPlugin(opts) {
|
|
|
3562
3612
|
},
|
|
3563
3613
|
transformIndexHtml(html) {
|
|
3564
3614
|
if (isBuild) {
|
|
3565
|
-
const
|
|
3566
|
-
|
|
3615
|
+
const stripped = html.replace(/\s*<link[^>]*href=["'][^"']*virtual:truss\.css["'][^>]*\/?>/g, "").replace(/\s*<link[^>]*href=["'][^"']*__TRUSS_CSS_HASH__["'][^>]*\/?>/g, "").replace(/\s*<link[^>]*href=["'][^"']*\/assets\/truss-[0-9a-f]+\.css["'][^>]*\/?>/g, "");
|
|
3616
|
+
const link = `<link rel="stylesheet" href="${TRUSS_CSS_PLACEHOLDER}">`;
|
|
3617
|
+
return stripped.replace("</head>", ` ${link}
|
|
3567
3618
|
</head>`);
|
|
3568
3619
|
}
|
|
3569
3620
|
const tag = `<script type="module" src="/${VIRTUAL_RUNTIME_ID}"></script>`;
|
|
@@ -3680,11 +3731,27 @@ __injectTrussCSS(${JSON.stringify(css)});
|
|
|
3680
3731
|
if (!isBuild) return;
|
|
3681
3732
|
const css = collectCss();
|
|
3682
3733
|
if (!css) return;
|
|
3734
|
+
const hash = createHash("sha256").update(css).digest("hex").slice(0, 8);
|
|
3735
|
+
const fileName = `assets/truss-${hash}.css`;
|
|
3736
|
+
emittedCssFileName = fileName;
|
|
3683
3737
|
this.emitFile({
|
|
3684
3738
|
type: "asset",
|
|
3685
|
-
fileName
|
|
3739
|
+
fileName,
|
|
3686
3740
|
source: css
|
|
3687
3741
|
});
|
|
3742
|
+
},
|
|
3743
|
+
/** Patch HTML files on disk to replace the CSS placeholder with the hashed filename. */
|
|
3744
|
+
writeBundle(options, _bundle) {
|
|
3745
|
+
if (!emittedCssFileName) return;
|
|
3746
|
+
const outDir = options.dir || join2(projectRoot, "dist");
|
|
3747
|
+
for (const entry of readdirSync(outDir)) {
|
|
3748
|
+
if (!entry.endsWith(".html")) continue;
|
|
3749
|
+
const htmlPath = join2(outDir, entry);
|
|
3750
|
+
const html = readFileSync3(htmlPath, "utf8");
|
|
3751
|
+
if (html.includes(TRUSS_CSS_PLACEHOLDER)) {
|
|
3752
|
+
writeFileSync2(htmlPath, html.replace(TRUSS_CSS_PLACEHOLDER, `/${emittedCssFileName}`), "utf8");
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3688
3755
|
}
|
|
3689
3756
|
};
|
|
3690
3757
|
}
|