@homebound/truss 2.3.5 → 2.5.0
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 +49 -3
- package/build/plugin/index.js +163 -24
- package/build/plugin/index.js.map +1 -1
- package/package.json +1 -1
package/build/plugin/index.d.ts
CHANGED
|
@@ -34,11 +34,57 @@ type TrussMappingEntry =
|
|
|
34
34
|
chain: string[];
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
interface TrussEsbuildPluginOptions {
|
|
38
|
+
/** Path to the Css.json mapping file (relative to cwd or absolute). */
|
|
39
|
+
mapping: string;
|
|
40
|
+
/** Output path for the generated truss.css (relative to outDir or absolute). Defaults to `truss.css`. */
|
|
41
|
+
outputCss?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* esbuild plugin that transforms `Css.*.$` expressions and emits a `truss.css` file.
|
|
45
|
+
*
|
|
46
|
+
* Designed for library builds using tsup/esbuild. Transforms source files
|
|
47
|
+
* during the build and writes an annotated `truss.css` alongside the output
|
|
48
|
+
* that consuming applications can merge via the Vite plugin's `libraries` option.
|
|
49
|
+
*
|
|
50
|
+
* Usage with tsup:
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { trussEsbuildPlugin } from "@homebound/truss/plugin";
|
|
53
|
+
*
|
|
54
|
+
* export default defineConfig({
|
|
55
|
+
* esbuildPlugins: [trussEsbuildPlugin({ mapping: "./src/Css.json" })],
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function trussEsbuildPlugin(opts: TrussEsbuildPluginOptions): {
|
|
60
|
+
name: string;
|
|
61
|
+
setup(build: EsbuildPluginBuild): void;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Minimal esbuild plugin types so we don't need esbuild as a dependency.
|
|
65
|
+
*
|
|
66
|
+
* These match the subset of the esbuild Plugin API that we use.
|
|
67
|
+
*/
|
|
68
|
+
interface EsbuildPluginBuild {
|
|
69
|
+
initialOptions: {
|
|
70
|
+
outdir?: string;
|
|
71
|
+
};
|
|
72
|
+
onLoad(options: {
|
|
73
|
+
filter: RegExp;
|
|
74
|
+
}, callback: (args: {
|
|
75
|
+
path: string;
|
|
76
|
+
}) => {
|
|
77
|
+
contents: string;
|
|
78
|
+
loader: string;
|
|
79
|
+
} | undefined): void;
|
|
80
|
+
onEnd(callback: () => void): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
37
83
|
interface TrussPluginOptions {
|
|
38
84
|
/** Path to the Css.json mapping file used for transforming files (relative to project root or absolute). */
|
|
39
85
|
mapping: string;
|
|
40
|
-
/**
|
|
41
|
-
|
|
86
|
+
/** Paths to pre-compiled truss.css files from libraries to merge into the app's CSS. */
|
|
87
|
+
libraries?: string[];
|
|
42
88
|
}
|
|
43
89
|
interface TrussVitePlugin {
|
|
44
90
|
name: string;
|
|
@@ -72,4 +118,4 @@ declare function trussPlugin(opts: TrussPluginOptions): TrussVitePlugin;
|
|
|
72
118
|
/** Load a truss mapping file synchronously (for tests). */
|
|
73
119
|
declare function loadMapping(path: string): TrussMapping;
|
|
74
120
|
|
|
75
|
-
export { type TrussMapping, type TrussMappingEntry, type TrussPluginOptions, type TrussVitePlugin, loadMapping, trussPlugin };
|
|
121
|
+
export { type TrussEsbuildPluginOptions, type TrussMapping, type TrussMappingEntry, type TrussPluginOptions, type TrussVitePlugin, loadMapping, trussEsbuildPlugin, trussPlugin };
|
package/build/plugin/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/plugin/index.ts
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
3
|
-
import { resolve, dirname, isAbsolute, join } from "path";
|
|
2
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync } from "fs";
|
|
3
|
+
import { resolve as resolve2, dirname, isAbsolute, join as join2 } from "path";
|
|
4
4
|
|
|
5
5
|
// src/plugin/emit-truss.ts
|
|
6
6
|
import * as t from "@babel/types";
|
|
@@ -838,13 +838,18 @@ function collectVariableRules(rules, seg, mapping) {
|
|
|
838
838
|
function generateCssText(rules) {
|
|
839
839
|
const allRules = Array.from(rules.values());
|
|
840
840
|
sortRulesByPriority(allRules);
|
|
841
|
+
const priorities = allRules.map(computeRulePriority);
|
|
841
842
|
const lines = [];
|
|
842
|
-
for (
|
|
843
|
+
for (let i = 0; i < allRules.length; i++) {
|
|
844
|
+
const rule = allRules[i];
|
|
845
|
+
const priority = priorities[i];
|
|
846
|
+
lines.push(`/* @truss p:${priority} c:${rule.className} */`);
|
|
843
847
|
lines.push(formatRule(rule));
|
|
844
848
|
}
|
|
845
849
|
for (const rule of allRules) {
|
|
846
850
|
for (const declaration of getRuleDeclarations(rule)) {
|
|
847
851
|
if (declaration.cssVarName) {
|
|
852
|
+
lines.push(`/* @truss @property */`);
|
|
848
853
|
lines.push(`@property ${declaration.cssVarName} { syntax: "*"; inherits: false; }`);
|
|
849
854
|
}
|
|
850
855
|
}
|
|
@@ -2791,6 +2796,133 @@ function toVirtualCssSpecifier(source) {
|
|
|
2791
2796
|
return `${source}?truss-css`;
|
|
2792
2797
|
}
|
|
2793
2798
|
|
|
2799
|
+
// src/plugin/merge-css.ts
|
|
2800
|
+
import { readFileSync } from "fs";
|
|
2801
|
+
var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
|
|
2802
|
+
var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
|
|
2803
|
+
var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
|
|
2804
|
+
function parseTrussCss(cssText) {
|
|
2805
|
+
const lines = cssText.split("\n");
|
|
2806
|
+
const rules = [];
|
|
2807
|
+
const properties = [];
|
|
2808
|
+
let i = 0;
|
|
2809
|
+
while (i < lines.length) {
|
|
2810
|
+
const line = lines[i].trim();
|
|
2811
|
+
const ruleMatch = RULE_ANNOTATION_RE.exec(line);
|
|
2812
|
+
if (ruleMatch) {
|
|
2813
|
+
const priority = parseFloat(ruleMatch[1]);
|
|
2814
|
+
const className = ruleMatch[2];
|
|
2815
|
+
i++;
|
|
2816
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
2817
|
+
if (i < lines.length) {
|
|
2818
|
+
rules.push({ priority, className, cssText: lines[i].trim() });
|
|
2819
|
+
}
|
|
2820
|
+
i++;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (PROPERTY_ANNOTATION_RE.test(line)) {
|
|
2824
|
+
i++;
|
|
2825
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
2826
|
+
if (i < lines.length) {
|
|
2827
|
+
const propLine = lines[i].trim();
|
|
2828
|
+
const varMatch = PROPERTY_VAR_RE.exec(propLine);
|
|
2829
|
+
if (varMatch) {
|
|
2830
|
+
properties.push({ cssText: propLine, varName: varMatch[1] });
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
i++;
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
i++;
|
|
2837
|
+
}
|
|
2838
|
+
return { rules, properties };
|
|
2839
|
+
}
|
|
2840
|
+
function readTrussCss(filePath) {
|
|
2841
|
+
const content = readFileSync(filePath, "utf8");
|
|
2842
|
+
return parseTrussCss(content);
|
|
2843
|
+
}
|
|
2844
|
+
function mergeTrussCss(sources) {
|
|
2845
|
+
const seenClasses = /* @__PURE__ */ new Set();
|
|
2846
|
+
const allRules = [];
|
|
2847
|
+
const seenProperties = /* @__PURE__ */ new Set();
|
|
2848
|
+
const allProperties = [];
|
|
2849
|
+
for (const source of sources) {
|
|
2850
|
+
for (const rule of source.rules) {
|
|
2851
|
+
if (!seenClasses.has(rule.className)) {
|
|
2852
|
+
seenClasses.add(rule.className);
|
|
2853
|
+
allRules.push(rule);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
for (const prop of source.properties) {
|
|
2857
|
+
if (!seenProperties.has(prop.varName)) {
|
|
2858
|
+
seenProperties.add(prop.varName);
|
|
2859
|
+
allProperties.push(prop);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
allRules.sort(function(a, b) {
|
|
2864
|
+
const diff = a.priority - b.priority;
|
|
2865
|
+
if (diff !== 0) return diff;
|
|
2866
|
+
return a.className < b.className ? -1 : a.className > b.className ? 1 : 0;
|
|
2867
|
+
});
|
|
2868
|
+
const lines = [];
|
|
2869
|
+
for (const rule of allRules) {
|
|
2870
|
+
lines.push(`/* @truss p:${rule.priority} c:${rule.className} */`);
|
|
2871
|
+
lines.push(rule.cssText);
|
|
2872
|
+
}
|
|
2873
|
+
for (const prop of allProperties) {
|
|
2874
|
+
lines.push(`/* @truss @property */`);
|
|
2875
|
+
lines.push(prop.cssText);
|
|
2876
|
+
}
|
|
2877
|
+
return lines.join("\n");
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
// src/plugin/esbuild-plugin.ts
|
|
2881
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
2882
|
+
import { resolve, join } from "path";
|
|
2883
|
+
function trussEsbuildPlugin(opts) {
|
|
2884
|
+
const cssRegistry = /* @__PURE__ */ new Map();
|
|
2885
|
+
let mapping = null;
|
|
2886
|
+
let outDir;
|
|
2887
|
+
return {
|
|
2888
|
+
name: "truss",
|
|
2889
|
+
setup(build) {
|
|
2890
|
+
outDir = build.initialOptions.outdir ?? build.initialOptions.outdir;
|
|
2891
|
+
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, function(args) {
|
|
2892
|
+
const code = readFileSync2(args.path, "utf8");
|
|
2893
|
+
if (!code.includes("Css")) return void 0;
|
|
2894
|
+
if (!mapping) {
|
|
2895
|
+
mapping = loadMapping(resolve(process.cwd(), opts.mapping));
|
|
2896
|
+
}
|
|
2897
|
+
const result = transformTruss(code, args.path, mapping);
|
|
2898
|
+
if (!result) return void 0;
|
|
2899
|
+
if (result.rules) {
|
|
2900
|
+
for (const [className, rule] of result.rules) {
|
|
2901
|
+
if (!cssRegistry.has(className)) {
|
|
2902
|
+
cssRegistry.set(className, rule);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
return { contents: result.code, loader: loaderForPath(args.path) };
|
|
2907
|
+
});
|
|
2908
|
+
build.onEnd(function() {
|
|
2909
|
+
if (cssRegistry.size === 0) return;
|
|
2910
|
+
const css = generateCssText(cssRegistry);
|
|
2911
|
+
const cssFileName = opts.outputCss ?? "truss.css";
|
|
2912
|
+
const cssPath = resolve(outDir ?? join(process.cwd(), "dist"), cssFileName);
|
|
2913
|
+
mkdirSync(resolve(cssPath, ".."), { recursive: true });
|
|
2914
|
+
writeFileSync(cssPath, css, "utf8");
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
function loaderForPath(filePath) {
|
|
2920
|
+
if (filePath.endsWith(".tsx")) return "tsx";
|
|
2921
|
+
if (filePath.endsWith(".ts")) return "ts";
|
|
2922
|
+
if (filePath.endsWith(".jsx")) return "jsx";
|
|
2923
|
+
return "js";
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2794
2926
|
// src/plugin/index.ts
|
|
2795
2927
|
var VIRTUAL_CSS_PREFIX = "\0truss-css:";
|
|
2796
2928
|
var CSS_TS_QUERY = "?truss-css";
|
|
@@ -2803,12 +2935,12 @@ function trussPlugin(opts) {
|
|
|
2803
2935
|
let debug = false;
|
|
2804
2936
|
let isTest = false;
|
|
2805
2937
|
let isBuild = false;
|
|
2806
|
-
const
|
|
2938
|
+
const libraryPaths = opts.libraries ?? [];
|
|
2807
2939
|
const cssRegistry = /* @__PURE__ */ new Map();
|
|
2808
2940
|
let cssVersion = 0;
|
|
2809
2941
|
let lastSentVersion = 0;
|
|
2810
2942
|
function mappingPath() {
|
|
2811
|
-
return
|
|
2943
|
+
return resolve2(projectRoot || process.cwd(), opts.mapping);
|
|
2812
2944
|
}
|
|
2813
2945
|
function ensureMapping() {
|
|
2814
2946
|
if (!mapping) {
|
|
@@ -2816,8 +2948,22 @@ function trussPlugin(opts) {
|
|
|
2816
2948
|
}
|
|
2817
2949
|
return mapping;
|
|
2818
2950
|
}
|
|
2951
|
+
let libraryCache = null;
|
|
2952
|
+
function loadLibraries() {
|
|
2953
|
+
if (!libraryCache) {
|
|
2954
|
+
libraryCache = libraryPaths.map(function(libPath) {
|
|
2955
|
+
const resolved = resolve2(projectRoot || process.cwd(), libPath);
|
|
2956
|
+
return readTrussCss(resolved);
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
return libraryCache;
|
|
2960
|
+
}
|
|
2819
2961
|
function collectCss() {
|
|
2820
|
-
|
|
2962
|
+
const appCss = generateCssText(cssRegistry);
|
|
2963
|
+
const libs = loadLibraries();
|
|
2964
|
+
if (libs.length === 0) return appCss;
|
|
2965
|
+
const appParsed = parseTrussCss(appCss);
|
|
2966
|
+
return mergeTrussCss([...libs, appParsed]);
|
|
2821
2967
|
}
|
|
2822
2968
|
return {
|
|
2823
2969
|
name: "truss",
|
|
@@ -2831,6 +2977,7 @@ function trussPlugin(opts) {
|
|
|
2831
2977
|
buildStart() {
|
|
2832
2978
|
ensureMapping();
|
|
2833
2979
|
cssRegistry.clear();
|
|
2980
|
+
libraryCache = null;
|
|
2834
2981
|
cssVersion = 0;
|
|
2835
2982
|
lastSentVersion = 0;
|
|
2836
2983
|
},
|
|
@@ -2907,7 +3054,7 @@ function trussPlugin(opts) {
|
|
|
2907
3054
|
}
|
|
2908
3055
|
if (!id.startsWith(VIRTUAL_CSS_PREFIX)) return null;
|
|
2909
3056
|
const sourcePath = id.slice(VIRTUAL_CSS_PREFIX.length) + ".ts";
|
|
2910
|
-
const sourceCode =
|
|
3057
|
+
const sourceCode = readFileSync3(sourcePath, "utf8");
|
|
2911
3058
|
return transformCssTs(sourceCode, sourcePath, ensureMapping());
|
|
2912
3059
|
},
|
|
2913
3060
|
transform(code, id) {
|
|
@@ -2917,7 +3064,7 @@ function trussPlugin(opts) {
|
|
|
2917
3064
|
const hasCssDsl = rewrittenCode.includes("Css");
|
|
2918
3065
|
if (!hasCssDsl && !rewrittenImports.changed) return null;
|
|
2919
3066
|
const fileId = stripQueryAndHash(id);
|
|
2920
|
-
if (isNodeModulesFile(fileId)
|
|
3067
|
+
if (isNodeModulesFile(fileId)) {
|
|
2921
3068
|
return null;
|
|
2922
3069
|
}
|
|
2923
3070
|
if (fileId.endsWith(".css.ts")) {
|
|
@@ -2971,15 +3118,15 @@ function trussPlugin(opts) {
|
|
|
2971
3118
|
if (!isBuild) return;
|
|
2972
3119
|
const css = collectCss();
|
|
2973
3120
|
if (!css) return;
|
|
2974
|
-
const outDir = options.dir ||
|
|
2975
|
-
const trussPath =
|
|
3121
|
+
const outDir = options.dir || join2(projectRoot, "dist");
|
|
3122
|
+
const trussPath = join2(outDir, "truss.css");
|
|
2976
3123
|
if (!existsSync(trussPath)) {
|
|
2977
3124
|
const alreadyEmitted = Object.keys(bundle).some(function(key) {
|
|
2978
3125
|
const asset = bundle[key];
|
|
2979
3126
|
return asset.type === "asset" && key.endsWith(".css") && typeof asset.source === "string" && asset.source.includes(css);
|
|
2980
3127
|
});
|
|
2981
3128
|
if (!alreadyEmitted) {
|
|
2982
|
-
|
|
3129
|
+
writeFileSync2(trussPath, css, "utf8");
|
|
2983
3130
|
}
|
|
2984
3131
|
}
|
|
2985
3132
|
}
|
|
@@ -2990,9 +3137,9 @@ function resolveImportPath(source, importer, projectRoot) {
|
|
|
2990
3137
|
return source;
|
|
2991
3138
|
}
|
|
2992
3139
|
if (importer) {
|
|
2993
|
-
return
|
|
3140
|
+
return resolve2(dirname(importer), source);
|
|
2994
3141
|
}
|
|
2995
|
-
return
|
|
3142
|
+
return resolve2(projectRoot || process.cwd(), source);
|
|
2996
3143
|
}
|
|
2997
3144
|
function stripQueryAndHash(id) {
|
|
2998
3145
|
const queryIndex = id.indexOf("?");
|
|
@@ -3007,23 +3154,15 @@ function stripQueryAndHash(id) {
|
|
|
3007
3154
|
return cleanId;
|
|
3008
3155
|
}
|
|
3009
3156
|
function isNodeModulesFile(filePath) {
|
|
3010
|
-
return
|
|
3011
|
-
}
|
|
3012
|
-
function isWhitelistedExternalPackageFile(filePath, externalPackages) {
|
|
3013
|
-
const normalizedPath = normalizePath(filePath);
|
|
3014
|
-
return externalPackages.some(function(pkg) {
|
|
3015
|
-
return normalizedPath.includes(`/node_modules/${pkg}/`);
|
|
3016
|
-
});
|
|
3017
|
-
}
|
|
3018
|
-
function normalizePath(path) {
|
|
3019
|
-
return path.replace(/\\/g, "/");
|
|
3157
|
+
return filePath.replace(/\\/g, "/").includes("/node_modules/");
|
|
3020
3158
|
}
|
|
3021
3159
|
function loadMapping(path) {
|
|
3022
|
-
const raw =
|
|
3160
|
+
const raw = readFileSync3(path, "utf8");
|
|
3023
3161
|
return JSON.parse(raw);
|
|
3024
3162
|
}
|
|
3025
3163
|
export {
|
|
3026
3164
|
loadMapping,
|
|
3165
|
+
trussEsbuildPlugin,
|
|
3027
3166
|
trussPlugin
|
|
3028
3167
|
};
|
|
3029
3168
|
//# sourceMappingURL=index.js.map
|