@homebound/truss 2.29.8 → 2.29.10
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 +7 -2
- package/build/plugin/index.js +104 -23
- package/build/plugin/index.js.map +1 -1
- package/build/vitest.d.ts +2 -5
- package/package.json +13 -14
- package/tsconfig.tsbuildinfo +1 -1
package/build/plugin/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PluginContext } from 'rollup';
|
|
2
|
+
|
|
1
3
|
/** The shape of the Css.json mapping file consumed by the Vite plugin. */
|
|
2
4
|
interface TrussMapping {
|
|
3
5
|
increment: number;
|
|
@@ -91,15 +93,18 @@ interface TrussPluginOptions {
|
|
|
91
93
|
mapping: string;
|
|
92
94
|
/** Paths to pre-compiled truss.css files from libraries to merge into the app's CSS. */
|
|
93
95
|
libraries?: string[];
|
|
96
|
+
/** Unsupported patterns fail builds by default; dev transforms warn and keep serving the page. */
|
|
97
|
+
unsupportedPattern?: "error" | "warn";
|
|
94
98
|
}
|
|
99
|
+
type DiagnosticContext = Pick<PluginContext, "warn">;
|
|
95
100
|
interface TrussVitePlugin {
|
|
96
101
|
name: string;
|
|
97
102
|
enforce?: "pre" | "post";
|
|
98
103
|
configResolved?: (config: any) => void;
|
|
99
104
|
buildStart?: () => void;
|
|
100
105
|
resolveId?: (source: string, importer: string | undefined) => string | null;
|
|
101
|
-
load?: (id: string) => string | null;
|
|
102
|
-
transform?: (code: string, id: string) => {
|
|
106
|
+
load?: (this: DiagnosticContext, id: string) => string | null;
|
|
107
|
+
transform?: (this: DiagnosticContext, code: string, id: string) => {
|
|
103
108
|
code: string;
|
|
104
109
|
map: any;
|
|
105
110
|
} | null;
|
package/build/plugin/index.js
CHANGED
|
@@ -200,11 +200,9 @@ function toImportSpecifier(entry) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// src/plugin/babel-utils.ts
|
|
203
|
-
import
|
|
203
|
+
import generate from "@babel/generator";
|
|
204
204
|
import { parse } from "@babel/parser";
|
|
205
|
-
import
|
|
206
|
-
var generate = _generate.default ?? _generate;
|
|
207
|
-
var traverse = _traverse.default ?? _traverse;
|
|
205
|
+
import traverse from "@babel/traverse";
|
|
208
206
|
function parseModule(code, filename) {
|
|
209
207
|
return parse(code, {
|
|
210
208
|
sourceType: "module",
|
|
@@ -332,11 +330,44 @@ function resetConditionContext(context) {
|
|
|
332
330
|
Object.assign(context, emptyConditionContext());
|
|
333
331
|
}
|
|
334
332
|
|
|
333
|
+
// src/plugin/unknown-abbreviation.ts
|
|
334
|
+
var UnknownAbbreviationError = class extends UnsupportedPatternError {
|
|
335
|
+
constructor(abbreviation, candidates) {
|
|
336
|
+
const suggestion = closestAbbreviation(abbreviation, candidates);
|
|
337
|
+
super(`Unknown abbreviation "${abbreviation}"${suggestion ? `. Did you mean "${suggestion}"?` : ""}`);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
function closestAbbreviation(abbreviation, candidates) {
|
|
341
|
+
let closest;
|
|
342
|
+
let bestDistance = 3;
|
|
343
|
+
for (const candidate of candidates) {
|
|
344
|
+
if (Math.abs(candidate.length - abbreviation.length) >= bestDistance) continue;
|
|
345
|
+
let previous = Array.from({ length: candidate.length + 1 }, (_, index) => index);
|
|
346
|
+
for (let i = 1; i <= abbreviation.length; i++) {
|
|
347
|
+
const current = [i];
|
|
348
|
+
for (let j = 1; j <= candidate.length; j++) {
|
|
349
|
+
current[j] = Math.min(
|
|
350
|
+
current[j - 1] + 1,
|
|
351
|
+
previous[j] + 1,
|
|
352
|
+
previous[j - 1] + (abbreviation[i - 1] === candidate[j - 1] ? 0 : 1)
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
previous = current;
|
|
356
|
+
}
|
|
357
|
+
const distance = previous[candidate.length];
|
|
358
|
+
if (distance < bestDistance) {
|
|
359
|
+
closest = candidate;
|
|
360
|
+
bestDistance = distance;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return closest;
|
|
364
|
+
}
|
|
365
|
+
|
|
335
366
|
// src/plugin/resolve-entry.ts
|
|
336
367
|
function requireEntry(mapping, abbr) {
|
|
337
368
|
const entry = mapping.abbreviations[abbr];
|
|
338
369
|
if (!entry) {
|
|
339
|
-
throw new
|
|
370
|
+
throw new UnknownAbbreviationError(abbr, Object.keys(mapping.abbreviations));
|
|
340
371
|
}
|
|
341
372
|
return entry;
|
|
342
373
|
}
|
|
@@ -2735,12 +2766,28 @@ function getTopLevelVariableDeclaration(node) {
|
|
|
2735
2766
|
return null;
|
|
2736
2767
|
}
|
|
2737
2768
|
|
|
2769
|
+
// src/plugin/diagnostic.ts
|
|
2770
|
+
var Diagnostic = class extends Error {
|
|
2771
|
+
id;
|
|
2772
|
+
loc;
|
|
2773
|
+
constructor(message, filename, node) {
|
|
2774
|
+
const start = node.loc?.start;
|
|
2775
|
+
const location = start ? `${filename}:${start.line}:${start.column + 1}` : filename;
|
|
2776
|
+
super(`${location}: ${message}`);
|
|
2777
|
+
this.id = filename;
|
|
2778
|
+
this.loc = start ? { file: filename, line: start.line, column: start.column } : void 0;
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
|
|
2738
2782
|
// src/plugin/transform-css.ts
|
|
2739
|
-
function transformCssTs(code, filename, mapping) {
|
|
2783
|
+
function transformCssTs(code, filename, mapping, options = {}) {
|
|
2740
2784
|
const ast = parseModule(code, filename);
|
|
2741
2785
|
const cssBindingName = findCssImportBinding(ast);
|
|
2742
2786
|
const cssExport = findNamedCssExportObject(ast);
|
|
2743
2787
|
if (!cssExport) {
|
|
2788
|
+
options.onDiagnostic?.(
|
|
2789
|
+
new Diagnostic("expected `export const css = { ... }` with an object literal", filename, ast.program)
|
|
2790
|
+
);
|
|
2744
2791
|
return `/* [truss] ${filename}: expected \`export const css = { ... }\` with an object literal */
|
|
2745
2792
|
`;
|
|
2746
2793
|
}
|
|
@@ -2748,16 +2795,16 @@ function transformCssTs(code, filename, mapping) {
|
|
|
2748
2795
|
const stringBindings = collectStaticStringBindings(ast);
|
|
2749
2796
|
for (const prop of cssExport.properties) {
|
|
2750
2797
|
if (t12.isSpreadElement(prop)) {
|
|
2751
|
-
rules.push(
|
|
2798
|
+
rules.push(unsupported("spread elements in css.ts export", prop));
|
|
2752
2799
|
continue;
|
|
2753
2800
|
}
|
|
2754
2801
|
if (!t12.isObjectProperty(prop)) {
|
|
2755
|
-
rules.push(
|
|
2802
|
+
rules.push(unsupported("non-property in css.ts export", prop));
|
|
2756
2803
|
continue;
|
|
2757
2804
|
}
|
|
2758
2805
|
const selector = objectPropertyStringKey(prop, stringBindings);
|
|
2759
2806
|
if (selector === null) {
|
|
2760
|
-
rules.push(
|
|
2807
|
+
rules.push(unsupported("non-string-literal key in css.ts export", prop));
|
|
2761
2808
|
continue;
|
|
2762
2809
|
}
|
|
2763
2810
|
const valueNode = prop.value;
|
|
@@ -2767,21 +2814,25 @@ function transformCssTs(code, filename, mapping) {
|
|
|
2767
2814
|
continue;
|
|
2768
2815
|
}
|
|
2769
2816
|
if (!t12.isExpression(valueNode)) {
|
|
2770
|
-
rules.push(
|
|
2817
|
+
rules.push(unsupported(`"${selector}" value is not an expression`, valueNode));
|
|
2771
2818
|
continue;
|
|
2772
2819
|
}
|
|
2773
2820
|
if (!cssBindingName) {
|
|
2774
|
-
rules.push(
|
|
2821
|
+
rules.push(unsupported(`"${selector}" \u2014 Css.*.$ chain requires a Css import`, valueNode));
|
|
2775
2822
|
continue;
|
|
2776
2823
|
}
|
|
2777
|
-
const cssResult = resolveCssExpression(valueNode, cssBindingName, mapping
|
|
2824
|
+
const cssResult = resolveCssExpression(valueNode, cssBindingName, mapping);
|
|
2778
2825
|
if ("error" in cssResult) {
|
|
2779
|
-
rules.push(
|
|
2826
|
+
rules.push(unsupported(`"${selector}" \u2014 ${cssResult.error}`, valueNode));
|
|
2780
2827
|
continue;
|
|
2781
2828
|
}
|
|
2782
2829
|
rules.push(formatCssRule(selector, cssResult.declarations));
|
|
2783
2830
|
}
|
|
2784
2831
|
return rules.join("\n\n") + "\n";
|
|
2832
|
+
function unsupported(message, node) {
|
|
2833
|
+
options.onDiagnostic?.(new Diagnostic(message, filename, node));
|
|
2834
|
+
return `/* [truss] unsupported: ${message} */`;
|
|
2835
|
+
}
|
|
2785
2836
|
}
|
|
2786
2837
|
function findNamedCssExportObject(ast) {
|
|
2787
2838
|
for (const node of ast.program.body) {
|
|
@@ -2811,7 +2862,7 @@ function extractStaticStringValue(node, cssBindingName) {
|
|
|
2811
2862
|
}
|
|
2812
2863
|
return null;
|
|
2813
2864
|
}
|
|
2814
|
-
function resolveCssExpression(node, cssBindingName, mapping
|
|
2865
|
+
function resolveCssExpression(node, cssBindingName, mapping) {
|
|
2815
2866
|
const chain = extractDollarChain(node, cssBindingName);
|
|
2816
2867
|
if (!chain) {
|
|
2817
2868
|
return { error: "value must be a Css.*.$ expression" };
|
|
@@ -3377,10 +3428,18 @@ function transformTruss(code, filename, mapping, options = {}) {
|
|
|
3377
3428
|
return;
|
|
3378
3429
|
}
|
|
3379
3430
|
const resolveCssChainReference2 = buildCssChainReferenceResolver(path, cssBindingName);
|
|
3380
|
-
const resolvedChain = resolveFullChain(
|
|
3431
|
+
const resolvedChain = resolveFullChain(
|
|
3432
|
+
{
|
|
3433
|
+
mapping,
|
|
3434
|
+
cssBindingName,
|
|
3435
|
+
resolveCssChainReference: resolveCssChainReference2
|
|
3436
|
+
},
|
|
3437
|
+
chain
|
|
3438
|
+
);
|
|
3381
3439
|
sites.push({ path, resolvedChain });
|
|
3382
3440
|
const line = path.node.loc?.start.line ?? null;
|
|
3383
3441
|
for (const err of resolvedChain.errors) {
|
|
3442
|
+
options.onDiagnostic?.(new Diagnostic(err, filename, path.node));
|
|
3384
3443
|
errorMessages.push({ message: err, line });
|
|
3385
3444
|
}
|
|
3386
3445
|
},
|
|
@@ -3630,9 +3689,9 @@ function createTrussTransformSession(options) {
|
|
|
3630
3689
|
arbitraryCssRegistry.clear();
|
|
3631
3690
|
libraryCache = null;
|
|
3632
3691
|
}
|
|
3633
|
-
function updateArbitraryCssRegistry(sourcePath, sourceCode) {
|
|
3692
|
+
function updateArbitraryCssRegistry(sourcePath, sourceCode, diagnostics = {}) {
|
|
3634
3693
|
sourcePath = resolve2(sourcePath).replace(/\\/g, "/");
|
|
3635
|
-
const css = transformCssTs(sourceCode, sourcePath, ensureMapping()).trim();
|
|
3694
|
+
const css = transformCssTs(sourceCode, sourcePath, ensureMapping(), diagnostics).trim();
|
|
3636
3695
|
if (css.length > 0) {
|
|
3637
3696
|
const prev = arbitraryCssRegistry.get(sourcePath);
|
|
3638
3697
|
arbitraryCssRegistry.set(sourcePath, css);
|
|
@@ -3697,6 +3756,11 @@ import * as t16 from "@babel/types";
|
|
|
3697
3756
|
import { readFileSync as readFileSync3, writeFileSync, mkdirSync } from "fs";
|
|
3698
3757
|
import { resolve as resolve3, join } from "path";
|
|
3699
3758
|
function trussEsbuildPlugin(opts) {
|
|
3759
|
+
const diagnostics = {
|
|
3760
|
+
onDiagnostic(error) {
|
|
3761
|
+
throw error;
|
|
3762
|
+
}
|
|
3763
|
+
};
|
|
3700
3764
|
const session = createTrussTransformSession({
|
|
3701
3765
|
mappingPath() {
|
|
3702
3766
|
return resolve3(process.cwd(), opts.mapping);
|
|
@@ -3712,11 +3776,11 @@ function trussEsbuildPlugin(opts) {
|
|
|
3712
3776
|
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, (args) => {
|
|
3713
3777
|
const code = readFileSync3(args.path, "utf8");
|
|
3714
3778
|
if (args.path.endsWith(".css.ts")) {
|
|
3715
|
-
session.updateArbitraryCssRegistry(args.path, code);
|
|
3779
|
+
session.updateArbitraryCssRegistry(args.path, code, diagnostics);
|
|
3716
3780
|
return { contents: code, loader: loaderForPath(args.path) };
|
|
3717
3781
|
}
|
|
3718
3782
|
if (!code.includes("Css") && !code.includes("css=")) return void 0;
|
|
3719
|
-
const result = session.transformCode(code, args.path);
|
|
3783
|
+
const result = session.transformCode(code, args.path, diagnostics);
|
|
3720
3784
|
if (!result) return void 0;
|
|
3721
3785
|
return { contents: result.code, loader: loaderForPath(args.path) };
|
|
3722
3786
|
});
|
|
@@ -3755,6 +3819,7 @@ function trussPlugin(opts) {
|
|
|
3755
3819
|
let debug = false;
|
|
3756
3820
|
let isTest = false;
|
|
3757
3821
|
let isBuild = false;
|
|
3822
|
+
let devSocket;
|
|
3758
3823
|
const libraryPaths = opts.libraries ?? [];
|
|
3759
3824
|
let emittedCssFileName = null;
|
|
3760
3825
|
let cssVersion = 0;
|
|
@@ -3762,6 +3827,11 @@ function trussPlugin(opts) {
|
|
|
3762
3827
|
function mappingPath() {
|
|
3763
3828
|
return resolve4(projectRoot || process.cwd(), opts.mapping);
|
|
3764
3829
|
}
|
|
3830
|
+
function diagnostics(context) {
|
|
3831
|
+
return {
|
|
3832
|
+
onDiagnostic: (error) => reportDiagnostic(context, error)
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3765
3835
|
const session = createTrussTransformSession({
|
|
3766
3836
|
mappingPath,
|
|
3767
3837
|
projectRoot: () => projectRoot || process.cwd(),
|
|
@@ -3788,6 +3858,7 @@ function trussPlugin(opts) {
|
|
|
3788
3858
|
// -- Dev mode HMR --
|
|
3789
3859
|
configureServer(server) {
|
|
3790
3860
|
if (isTest) return;
|
|
3861
|
+
devSocket = server.ws;
|
|
3791
3862
|
server.middlewares.use((req, res, next) => {
|
|
3792
3863
|
if (req.url !== VIRTUAL_CSS_ENDPOINT) return next();
|
|
3793
3864
|
const css = session.collectCss();
|
|
@@ -3880,7 +3951,7 @@ __injectTrussCSS(${JSON.stringify(payload)});
|
|
|
3880
3951
|
}
|
|
3881
3952
|
if (id.startsWith(VIRTUAL_TEST_CSS_PREFIX)) {
|
|
3882
3953
|
const sourcePath2 = canonicalSourcePath(id.slice(VIRTUAL_TEST_CSS_PREFIX.length));
|
|
3883
|
-
session.updateArbitraryCssRegistry(sourcePath2, readFileSync4(sourcePath2, "utf8"));
|
|
3954
|
+
session.updateArbitraryCssRegistry(sourcePath2, readFileSync4(sourcePath2, "utf8"), diagnostics(this));
|
|
3884
3955
|
const payload = {
|
|
3885
3956
|
arbitraryRules: splitArbitraryCss(session.getArbitraryCss(sourcePath2)),
|
|
3886
3957
|
source: sourcePath2
|
|
@@ -3895,7 +3966,7 @@ __injectTrussCSS(${JSON.stringify(payload)});
|
|
|
3895
3966
|
if (!id.startsWith(VIRTUAL_CSS_PREFIX)) return null;
|
|
3896
3967
|
const sourcePath = id.slice(VIRTUAL_CSS_PREFIX.length) + ".ts";
|
|
3897
3968
|
const sourceCode = readFileSync4(sourcePath, "utf8");
|
|
3898
|
-
session.updateArbitraryCssRegistry(sourcePath, sourceCode);
|
|
3969
|
+
session.updateArbitraryCssRegistry(sourcePath, sourceCode, diagnostics(this));
|
|
3899
3970
|
return `/* [truss] ${sourcePath} \u2014 included via truss.css */`;
|
|
3900
3971
|
},
|
|
3901
3972
|
transform(code, id) {
|
|
@@ -3909,7 +3980,7 @@ __injectTrussCSS(${JSON.stringify(payload)});
|
|
|
3909
3980
|
import "${VIRTUAL_TEST_CSS_ID}";` : rewrittenImports.code;
|
|
3910
3981
|
const importsOnlyResult = rewrittenImports.changed || shouldBootstrapTestCss ? { code: transformedCode, map: null } : null;
|
|
3911
3982
|
if (fileId.endsWith(".css.ts")) {
|
|
3912
|
-
session.updateArbitraryCssRegistry(fileId, code);
|
|
3983
|
+
session.updateArbitraryCssRegistry(fileId, code, diagnostics(this));
|
|
3913
3984
|
if (isTest) {
|
|
3914
3985
|
const css = session.getArbitraryCss(fileId);
|
|
3915
3986
|
return { code: appendTestCssInjection(transformedCode, fileId, css), map: null };
|
|
@@ -3918,7 +3989,7 @@ import "${VIRTUAL_TEST_CSS_ID}";` : rewrittenImports.code;
|
|
|
3918
3989
|
}
|
|
3919
3990
|
const hasCssDsl = rewrittenImports.code.includes("Css") || rewrittenImports.code.includes("css=");
|
|
3920
3991
|
if (!hasCssDsl) return importsOnlyResult;
|
|
3921
|
-
const result = session.transformCode(transformedCode, fileId, { debug, injectCss: isTest });
|
|
3992
|
+
const result = session.transformCode(transformedCode, fileId, { debug, injectCss: isTest, ...diagnostics(this) });
|
|
3922
3993
|
return result ? { code: result.code, map: result.map } : importsOnlyResult;
|
|
3923
3994
|
},
|
|
3924
3995
|
// -- Production CSS emission --
|
|
@@ -3949,6 +4020,16 @@ import "${VIRTUAL_TEST_CSS_ID}";` : rewrittenImports.code;
|
|
|
3949
4020
|
}
|
|
3950
4021
|
}
|
|
3951
4022
|
};
|
|
4023
|
+
function reportDiagnostic(context, error) {
|
|
4024
|
+
if ((opts.unsupportedPattern ?? (isBuild ? "error" : "warn")) === "error") {
|
|
4025
|
+
throw error;
|
|
4026
|
+
}
|
|
4027
|
+
context.warn(error);
|
|
4028
|
+
devSocket?.send({
|
|
4029
|
+
type: "error",
|
|
4030
|
+
err: { message: error.message, stack: "", id: error.id, loc: error.loc, plugin: "truss" }
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
3952
4033
|
}
|
|
3953
4034
|
function resolveImportPath(source, importer, projectRoot) {
|
|
3954
4035
|
if (isAbsolute(source)) {
|