@animus-ui/vite-plugin 0.1.0-next.21 → 0.1.0-next.30
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/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +31 -262
- package/dist/resolve-global-styles.d.ts +3 -6
- package/dist/resolve-global-styles.d.ts.map +1 -1
- package/dist/resolve-global-styles.mjs +9 -85
- package/package.json +3 -2
- package/dist/theme-evaluator.d.ts +0 -33
- package/dist/theme-evaluator.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,6 @@ export interface AnimusExtractOptions {
|
|
|
11
11
|
include?: string[];
|
|
12
12
|
/** Glob patterns to exclude. */
|
|
13
13
|
exclude?: string[];
|
|
14
|
-
/** Package name patterns to resolve and include in analysis. Defaults to ['@animus-ui/*']. */
|
|
15
|
-
packagePatterns?: string[];
|
|
16
14
|
/** When true, extraction failures throw instead of warning. Use in CI to enforce full extraction. */
|
|
17
15
|
strict?: boolean;
|
|
18
16
|
/** Enable verbose logging. Also activatable via ANIMUS_DEBUG=1 env var. */
|
|
@@ -49,6 +47,5 @@ export interface AnimusExtractOptions {
|
|
|
49
47
|
layers?: string[];
|
|
50
48
|
}
|
|
51
49
|
export declare function animusExtract(options: AnimusExtractOptions): Plugin;
|
|
52
|
-
export { evaluateTheme, evaluateThemeObject } from './theme-evaluator';
|
|
53
50
|
export default animusExtract;
|
|
54
51
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAI3C,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qGAAqG;IACrG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AA6LD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CA46BnE;AAED,eAAe,aAAa,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -5,123 +5,12 @@ import { existsSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileS
|
|
|
5
5
|
import { tmpdir } from "os";
|
|
6
6
|
import { dirname, extname, join, relative, resolve } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { applyPrefix, applyUnitFallback, detectRuntime, execSubprocess, extractSystemFilePackages } from "@animus-ui/extract/pipeline";
|
|
8
9
|
import browserslist from "browserslist";
|
|
9
10
|
import { browserslistToTargets, transform } from "lightningcss";
|
|
10
11
|
//#region \0rolldown/runtime.js
|
|
11
12
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
12
13
|
//#endregion
|
|
13
|
-
//#region src/theme-evaluator.ts
|
|
14
|
-
/**
|
|
15
|
-
* Evaluate a theme object that has already been loaded/imported.
|
|
16
|
-
*
|
|
17
|
-
* If the theme has a `.manifest` property (from ThemeBuilder.build()),
|
|
18
|
-
* reads structured data directly — no re-flattening or var() pattern-matching.
|
|
19
|
-
* Falls back to legacy path for themes without manifests.
|
|
20
|
-
*/
|
|
21
|
-
function evaluateThemeObject(theme) {
|
|
22
|
-
if (theme.manifest && typeof theme.manifest === "object") {
|
|
23
|
-
const manifest = theme.manifest;
|
|
24
|
-
return {
|
|
25
|
-
scalesJson: JSON.stringify(manifest.tokenMap),
|
|
26
|
-
variableMapJson: JSON.stringify(manifest.variableMap),
|
|
27
|
-
variableCss: manifest.variableCss
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
console.warn("[animus] Theme has no .manifest property — using legacy evaluation. Update to @animus-ui/system >=0.2.0 for structured manifest support.");
|
|
31
|
-
return evaluateThemeObjectLegacy(theme);
|
|
32
|
-
}
|
|
33
|
-
/** Legacy theme evaluation — flattens theme and pattern-matches var() strings. */
|
|
34
|
-
function evaluateThemeObjectLegacy(theme) {
|
|
35
|
-
const flat = {};
|
|
36
|
-
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
37
|
-
if (scaleName.startsWith("_")) continue;
|
|
38
|
-
if (scaleName === "breakpoints") {
|
|
39
|
-
flattenScale(flat, "breakpoints", scaleValue);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (typeof scaleValue === "object" && scaleValue !== null && !Array.isArray(scaleValue)) flattenScale(flat, scaleName, scaleValue);
|
|
43
|
-
}
|
|
44
|
-
const variableMap = {};
|
|
45
|
-
for (const [tokenPath, value] of Object.entries(flat)) if (value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
|
|
46
|
-
const variableCss = buildVariableCss(theme);
|
|
47
|
-
return {
|
|
48
|
-
scalesJson: JSON.stringify(flat),
|
|
49
|
-
variableMapJson: JSON.stringify(variableMap),
|
|
50
|
-
variableCss
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Evaluate a theme module and flatten all scales to a JSON map.
|
|
55
|
-
* Format: { "scale_name.key": "css_value" }
|
|
56
|
-
*
|
|
57
|
-
* Uses Vite's ssrLoadModule to evaluate the theme at build start,
|
|
58
|
-
* so all computed scales (shadows, gradients that depend on colors)
|
|
59
|
-
* are resolved to their final values.
|
|
60
|
-
*/
|
|
61
|
-
async function evaluateTheme(ssrLoadModule, themePath) {
|
|
62
|
-
const mod = await ssrLoadModule(themePath);
|
|
63
|
-
const theme = mod.theme || mod.default;
|
|
64
|
-
if (!theme) throw new Error(`Theme module at ${themePath} must export 'theme' or a default export`);
|
|
65
|
-
return evaluateThemeObject(theme);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Build the full CSS variable block from a theme's _variables and _tokens.
|
|
69
|
-
*
|
|
70
|
-
* Produces:
|
|
71
|
-
* :root { --color-navy-500: #282a36; ... }
|
|
72
|
-
* [data-color-mode="dark"] { --color-primary: #ff80bf; ... }
|
|
73
|
-
*/
|
|
74
|
-
function buildVariableCss(theme) {
|
|
75
|
-
const parts = [];
|
|
76
|
-
if (theme._variables != null && typeof theme._variables === "object") {
|
|
77
|
-
const rootLines = [];
|
|
78
|
-
for (const categoryValue of Object.values(theme._variables)) {
|
|
79
|
-
if (categoryValue == null || typeof categoryValue !== "object") continue;
|
|
80
|
-
for (const [cssVar, cssValue] of Object.entries(categoryValue)) if (typeof cssValue === "string") rootLines.push(` ${cssVar}: ${cssValue};`);
|
|
81
|
-
}
|
|
82
|
-
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
83
|
-
}
|
|
84
|
-
if (theme._tokens?.modes != null && typeof theme._tokens.modes === "object") for (const [modeName, modeTokens] of Object.entries(theme._tokens.modes)) {
|
|
85
|
-
if (modeTokens == null || typeof modeTokens !== "object") continue;
|
|
86
|
-
const modeLines = [];
|
|
87
|
-
flattenModeTokens(modeLines, modeTokens, "");
|
|
88
|
-
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
89
|
-
}
|
|
90
|
-
return parts.join("\n\n");
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Recursively flatten mode token objects into CSS variable declarations.
|
|
94
|
-
*
|
|
95
|
-
* Examples:
|
|
96
|
-
* { primary: '#ff80bf' } → ' --color-primary: #ff80bf;'
|
|
97
|
-
* { background: { _: '#fff', muted: '#f5f5f5' } }
|
|
98
|
-
* → ' --color-background: #fff;'
|
|
99
|
-
* ' --color-background-muted: #f5f5f5;'
|
|
100
|
-
*
|
|
101
|
-
* The special key `_` maps to the base name (no suffix).
|
|
102
|
-
*/
|
|
103
|
-
function flattenModeTokens(lines, obj, prefix) {
|
|
104
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
105
|
-
let namePart;
|
|
106
|
-
if (key === "_") namePart = prefix;
|
|
107
|
-
else namePart = prefix ? `${prefix}-${key}` : key;
|
|
108
|
-
if (typeof value === "string" || typeof value === "number") lines.push(` --color-${namePart}: ${value};`);
|
|
109
|
-
else if (value != null && typeof value === "object" && !Array.isArray(value)) flattenModeTokens(lines, value, namePart);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function flattenScale(flat, prefix, obj, parentKey = "") {
|
|
113
|
-
if (typeof obj !== "object" || obj === null) {
|
|
114
|
-
const key = parentKey ? `${prefix}.${parentKey}` : prefix;
|
|
115
|
-
flat[key] = String(obj);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
119
|
-
const fullKey = parentKey ? `${parentKey}-${key}` : key;
|
|
120
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenScale(flat, prefix, value, fullKey);
|
|
121
|
-
else flat[`${prefix}.${fullKey}`] = String(value);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
14
|
//#region src/index.ts
|
|
126
15
|
const __pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
127
16
|
const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
|
|
@@ -168,93 +57,6 @@ function discoverFiles(dir, rootDir, excludePatterns) {
|
|
|
168
57
|
return results;
|
|
169
58
|
}
|
|
170
59
|
/**
|
|
171
|
-
* CSS properties that accept unitless numeric values.
|
|
172
|
-
* Matches @emotion/unitless and React DOM's style handling.
|
|
173
|
-
* Bare numerics on properties NOT in this set receive `px`.
|
|
174
|
-
*/
|
|
175
|
-
const UNITLESS_PROPERTIES = new Set([
|
|
176
|
-
"animation-iteration-count",
|
|
177
|
-
"border-image-outset",
|
|
178
|
-
"border-image-slice",
|
|
179
|
-
"border-image-width",
|
|
180
|
-
"box-flex",
|
|
181
|
-
"box-flex-group",
|
|
182
|
-
"box-ordinal-group",
|
|
183
|
-
"column-count",
|
|
184
|
-
"columns",
|
|
185
|
-
"flex",
|
|
186
|
-
"flex-grow",
|
|
187
|
-
"flex-positive",
|
|
188
|
-
"flex-shrink",
|
|
189
|
-
"flex-negative",
|
|
190
|
-
"flex-order",
|
|
191
|
-
"grid-area",
|
|
192
|
-
"grid-row",
|
|
193
|
-
"grid-row-end",
|
|
194
|
-
"grid-row-span",
|
|
195
|
-
"grid-row-start",
|
|
196
|
-
"grid-column",
|
|
197
|
-
"grid-column-end",
|
|
198
|
-
"grid-column-span",
|
|
199
|
-
"grid-column-start",
|
|
200
|
-
"font-weight",
|
|
201
|
-
"line-clamp",
|
|
202
|
-
"line-height",
|
|
203
|
-
"opacity",
|
|
204
|
-
"order",
|
|
205
|
-
"orphans",
|
|
206
|
-
"tab-size",
|
|
207
|
-
"widows",
|
|
208
|
-
"z-index",
|
|
209
|
-
"zoom",
|
|
210
|
-
"fill-opacity",
|
|
211
|
-
"flood-opacity",
|
|
212
|
-
"stop-opacity",
|
|
213
|
-
"stroke-dasharray",
|
|
214
|
-
"stroke-dashoffset",
|
|
215
|
-
"stroke-miterlimit",
|
|
216
|
-
"stroke-opacity",
|
|
217
|
-
"stroke-width"
|
|
218
|
-
]);
|
|
219
|
-
/**
|
|
220
|
-
* Append `px` to bare numeric values in CSS declarations for properties
|
|
221
|
-
* that expect length units. Unitless properties are preserved as-is.
|
|
222
|
-
* Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
|
|
223
|
-
*/
|
|
224
|
-
function applyUnitFallback(css) {
|
|
225
|
-
return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
|
|
226
|
-
if (UNITLESS_PROPERTIES.has(prop)) return match;
|
|
227
|
-
let depth = 0;
|
|
228
|
-
let fixed = "";
|
|
229
|
-
let i = 0;
|
|
230
|
-
while (i < value.length) if (value[i] === "(") {
|
|
231
|
-
depth++;
|
|
232
|
-
fixed += value[i];
|
|
233
|
-
i++;
|
|
234
|
-
} else if (value[i] === ")") {
|
|
235
|
-
depth--;
|
|
236
|
-
fixed += value[i];
|
|
237
|
-
i++;
|
|
238
|
-
} else if (depth > 0) {
|
|
239
|
-
fixed += value[i];
|
|
240
|
-
i++;
|
|
241
|
-
} else {
|
|
242
|
-
const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
|
|
243
|
-
if (numMatch) {
|
|
244
|
-
const num = numMatch[1];
|
|
245
|
-
const after = value[i + num.length];
|
|
246
|
-
if (after && /[a-z%]/i.test(after)) fixed += num;
|
|
247
|
-
else fixed += num + "px";
|
|
248
|
-
i += num.length;
|
|
249
|
-
} else {
|
|
250
|
-
fixed += value[i];
|
|
251
|
-
i++;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return fixed !== value ? `${prop}:${fixed};` : match;
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
60
|
* Resolve browser targets for Lightning CSS.
|
|
259
61
|
* Priority: explicit config → project browserslist → 'defaults' fallback.
|
|
260
62
|
*/
|
|
@@ -306,29 +108,6 @@ function buildFileEntriesFromCache(cache, changedPath) {
|
|
|
306
108
|
return entries;
|
|
307
109
|
}
|
|
308
110
|
/**
|
|
309
|
-
* Apply namespace prefix to a variable map and CSS variable declarations.
|
|
310
|
-
*
|
|
311
|
-
* Variable map: `{ "colors.ember": "--color-ember" }` → `{ "colors.ember": "--prefix-color-ember" }`
|
|
312
|
-
* Variable CSS: `--color-ember: #FF2800` → `--prefix-color-ember: #FF2800`
|
|
313
|
-
* `var(--color-ember)` → `var(--prefix-color-ember)`
|
|
314
|
-
*/
|
|
315
|
-
function applyPrefix(prefix, variableMapJson, variableCss) {
|
|
316
|
-
if (!prefix) return {
|
|
317
|
-
variableMapJson,
|
|
318
|
-
variableCss
|
|
319
|
-
};
|
|
320
|
-
const map = JSON.parse(variableMapJson);
|
|
321
|
-
const prefixed = {};
|
|
322
|
-
for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
|
|
323
|
-
let css = variableCss;
|
|
324
|
-
css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
|
|
325
|
-
css = css.replace(/var\(--([a-zA-Z][\w-]*)\)/g, `var(--${prefix}-$1)`);
|
|
326
|
-
return {
|
|
327
|
-
variableMapJson: JSON.stringify(prefixed),
|
|
328
|
-
variableCss: css
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
111
|
* The 7 Animus cascade layers in required order.
|
|
333
112
|
* Consumer-provided `layers` must contain these as a subsequence.
|
|
334
113
|
*/
|
|
@@ -363,6 +142,7 @@ function validateLayerOrder(layers) {
|
|
|
363
142
|
function animusExtract(options) {
|
|
364
143
|
let themeJson = "{}";
|
|
365
144
|
let variableMapJson = "{}";
|
|
145
|
+
let contextualVarsJson = "{}";
|
|
366
146
|
let configJson = "{}";
|
|
367
147
|
let groupRegistryJson = "{}";
|
|
368
148
|
let isProd = false;
|
|
@@ -381,6 +161,7 @@ function animusExtract(options) {
|
|
|
381
161
|
let storedManifest = null;
|
|
382
162
|
let storedManifestJson = "";
|
|
383
163
|
let resolvedComponentCss = "";
|
|
164
|
+
let systemResolveScript = null;
|
|
384
165
|
let storedSheets = null;
|
|
385
166
|
let storedSystemPropMapJson = "{}";
|
|
386
167
|
let storedDynamicPropsJson = "{}";
|
|
@@ -399,36 +180,28 @@ function animusExtract(options) {
|
|
|
399
180
|
resolvedSystemPath = resolve(rootDir, options.system);
|
|
400
181
|
try {
|
|
401
182
|
const ts = Date.now();
|
|
402
|
-
const tmpScript = join(tmpdir(), `animus-system-${ts}.js`);
|
|
403
183
|
const tmpOut = join(tmpdir(), `animus-system-${ts}.json`);
|
|
404
|
-
|
|
405
|
-
execSync(`bun run "${tmpScript}"`, {
|
|
406
|
-
cwd: rootDir,
|
|
407
|
-
encoding: "utf-8"
|
|
408
|
-
});
|
|
184
|
+
execSubprocess(`const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nif (!ds || !ds.toConfig) { throw new Error('Module does not export a SystemInstance with .serialize()'); }\nconst cfg = ds.toConfig();\nconst tokens = m.tokens || m.theme || null;\nconst serialized = tokens && typeof tokens.serialize === 'function' ? tokens.serialize() : null;\nconst globalStyleBlocks = {};\nfor (const [key, val] of Object.entries(m)) {\n if (val && typeof val === 'object' && val.__brand === 'GlobalStyleBlock') {\n globalStyleBlocks[key] = val.styles;\n }\n}\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify({\n propConfig: cfg.propConfig,\n groupRegistry: cfg.groupRegistry,\n serialized: serialized,\n transformNames: Object.keys(cfg.transforms || {}),\n globalStyleBlocks: Object.keys(globalStyleBlocks).length > 0 ? globalStyleBlocks : null\n}));\n`, rootDir);
|
|
409
185
|
const result = readFileSync(tmpOut, "utf-8");
|
|
410
186
|
try {
|
|
411
|
-
unlinkSync(tmpScript);
|
|
412
187
|
unlinkSync(tmpOut);
|
|
413
188
|
} catch {}
|
|
414
189
|
const parsed = JSON.parse(result);
|
|
415
190
|
configJson = parsed.propConfig;
|
|
416
191
|
groupRegistryJson = parsed.groupRegistry;
|
|
417
|
-
if (parsed.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
192
|
+
if (parsed.serialized) {
|
|
193
|
+
themeJson = parsed.serialized.scalesJson;
|
|
194
|
+
variableMapJson = parsed.serialized.variableMapJson;
|
|
195
|
+
variableCss = parsed.serialized.variableCss;
|
|
196
|
+
contextualVarsJson = parsed.serialized.contextualVarsJson;
|
|
422
197
|
if (options.prefix) {
|
|
423
198
|
const prefixed = applyPrefix(options.prefix, variableMapJson, variableCss);
|
|
424
199
|
variableMapJson = prefixed.variableMapJson;
|
|
425
200
|
variableCss = prefixed.variableCss;
|
|
426
201
|
}
|
|
427
|
-
} else
|
|
428
|
-
if (parsed.
|
|
429
|
-
|
|
430
|
-
const hasGlobal = parsed.globalStyles.global && Object.keys(parsed.globalStyles.global).length > 0;
|
|
431
|
-
if (hasReset || hasGlobal) try {
|
|
202
|
+
} else throw new Error("[animus-extract] Theme must be built with createTheme().build(). No .serialize() method found on tokens export.");
|
|
203
|
+
if (parsed.globalStyleBlocks) {
|
|
204
|
+
if (Object.keys(parsed.globalStyleBlocks).length > 0) try {
|
|
432
205
|
const gsTmp = Date.now();
|
|
433
206
|
const gsThemeFile = join(tmpdir(), `animus-gs-theme-${gsTmp}.json`);
|
|
434
207
|
const gsOut = join(tmpdir(), `animus-global-${gsTmp}.json`);
|
|
@@ -448,26 +221,27 @@ function animusExtract(options) {
|
|
|
448
221
|
} catch {}
|
|
449
222
|
const gsScriptPath = gsCandidates.find((p) => existsSync(p));
|
|
450
223
|
if (!gsScriptPath) throw new Error(`resolve-global-styles not found in: ${gsCandidates.join(", ")}`);
|
|
451
|
-
execSync(
|
|
224
|
+
execSync(`${detectRuntime()} run "${gsScriptPath}" "${resolvedSystemPath}" "${gsThemeFile}" "${gsOut}"`, {
|
|
452
225
|
cwd: rootDir,
|
|
453
226
|
encoding: "utf-8"
|
|
454
227
|
});
|
|
455
228
|
const gsResult = JSON.parse(readFileSync(gsOut, "utf-8"));
|
|
456
|
-
const parts =
|
|
229
|
+
const parts = Object.values(gsResult).filter(Boolean);
|
|
457
230
|
if (parts.length > 0) globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
|
|
458
231
|
try {
|
|
459
232
|
unlinkSync(gsThemeFile);
|
|
460
233
|
unlinkSync(gsOut);
|
|
461
234
|
} catch {}
|
|
462
235
|
} catch (e) {
|
|
236
|
+
if (options.strict) throw new Error(`[animus-extract] Global styles resolution failed: ${e?.message || e}`);
|
|
463
237
|
console.warn("[animus-extract] Global styles resolution failed:", e?.message || e);
|
|
464
238
|
}
|
|
465
239
|
}
|
|
466
240
|
if (parsed.transformNames && parsed.transformNames.length > 0) {
|
|
467
241
|
const tsTmp = Date.now();
|
|
468
242
|
const tmpResolve = join(tmpdir(), `animus-transforms-resolve-${tsTmp}.js`);
|
|
469
|
-
writeFileSync(tmpResolve, `const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.
|
|
470
|
-
|
|
243
|
+
writeFileSync(tmpResolve, `const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.toConfig();\nconst css = require('fs').readFileSync(process.argv[2], 'utf-8');\nconst resolved = css.replace(/__TRANSFORM__(\\w+)__(.+?)__/g, (_, name, rawValue) => {\n const fn = cfg.transforms[name];\n if (!fn) return rawValue;\n const value = rawValue !== '' && !isNaN(Number(rawValue)) ? Number(rawValue) : rawValue;\n const result = fn(value);\n return typeof result === 'object' ? JSON.stringify(result) : String(result);\n});\nrequire('fs').writeFileSync(process.argv[3], resolved);\n`);
|
|
244
|
+
systemResolveScript = tmpResolve;
|
|
471
245
|
}
|
|
472
246
|
} catch (e) {
|
|
473
247
|
if (options.strict) throw new Error(`[animus-extract] Failed to load system from ${resolvedSystemPath}: ${e}`);
|
|
@@ -476,12 +250,19 @@ function animusExtract(options) {
|
|
|
476
250
|
}
|
|
477
251
|
/**
|
|
478
252
|
* Run project analysis and update the manifest.
|
|
479
|
-
*
|
|
253
|
+
*
|
|
254
|
+
* Pipeline: NAPI analyzeProject → transform resolution (subprocess) → unit fallback.
|
|
255
|
+
* NAPI and unit fallback use @animus-ui/extract. Transform resolution stays as
|
|
256
|
+
* subprocess because it needs live JS functions from the system module (ESM isolation).
|
|
480
257
|
*/
|
|
481
258
|
function runAnalysis(fileEntries) {
|
|
482
259
|
try {
|
|
483
260
|
const { analyzeProject } = __require("@animus-ui/extract");
|
|
484
|
-
const
|
|
261
|
+
const emitterConfig = JSON.stringify({
|
|
262
|
+
runtime_import: "@animus-ui/system",
|
|
263
|
+
css_module_id: "virtual:animus/styles.css"
|
|
264
|
+
});
|
|
265
|
+
const manifestJson = analyzeProject(JSON.stringify(fileEntries), themeJson, variableMapJson, contextualVarsJson || null, configJson, groupRegistryJson, JSON.stringify(packageMap), !isProd, options.prefix || null, emitterConfig);
|
|
485
266
|
storedManifest = JSON.parse(manifestJson);
|
|
486
267
|
storedManifestJson = manifestJson;
|
|
487
268
|
storedSystemPropMapJson = JSON.stringify(storedManifest?.system_prop_map ?? {});
|
|
@@ -502,13 +283,12 @@ function animusExtract(options) {
|
|
|
502
283
|
bridgeInjected = false;
|
|
503
284
|
storedSheets = storedManifest?.sheets ?? null;
|
|
504
285
|
const rawCss = storedManifest?.css || "";
|
|
505
|
-
const systemResolveScript = globalThis.__animus_system_resolve_script;
|
|
506
286
|
if (systemResolveScript && rawCss.includes("__TRANSFORM__")) try {
|
|
507
287
|
const tsTmp = Date.now();
|
|
508
288
|
const tmpIn = join(tmpdir(), `animus-css-${tsTmp}.css`);
|
|
509
289
|
const tmpOut = join(tmpdir(), `animus-css-${tsTmp}.out.css`);
|
|
510
290
|
writeFileSync(tmpIn, rawCss);
|
|
511
|
-
execSync(
|
|
291
|
+
execSync(`${detectRuntime()} run "${systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
|
|
512
292
|
cwd: rootDir,
|
|
513
293
|
encoding: "utf-8"
|
|
514
294
|
});
|
|
@@ -518,6 +298,7 @@ function animusExtract(options) {
|
|
|
518
298
|
unlinkSync(tmpOut);
|
|
519
299
|
} catch {}
|
|
520
300
|
} catch (e) {
|
|
301
|
+
if (options.strict) throw new Error(`[animus-extract] Transform resolution failed: ${e?.message}`);
|
|
521
302
|
console.warn("[animus-extract] Transform resolution failed:", e?.message);
|
|
522
303
|
resolvedComponentCss = rawCss;
|
|
523
304
|
}
|
|
@@ -570,19 +351,7 @@ function animusExtract(options) {
|
|
|
570
351
|
});
|
|
571
352
|
} catch {}
|
|
572
353
|
const localFileCount = fileEntries.length;
|
|
573
|
-
const
|
|
574
|
-
const packageSpecifiers = /* @__PURE__ */ new Set();
|
|
575
|
-
for (const entry of fileEntries) {
|
|
576
|
-
const importRegex = /from\s+['"]([^'"]+)['"]/g;
|
|
577
|
-
let match;
|
|
578
|
-
while ((match = importRegex.exec(entry.source)) !== null) {
|
|
579
|
-
const source = match[1];
|
|
580
|
-
for (const pattern of patterns) {
|
|
581
|
-
const prefix = pattern.replace("*", "");
|
|
582
|
-
if (source.startsWith(prefix) || source === pattern.replace("/*", "")) packageSpecifiers.add(source);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
354
|
+
const packageSpecifiers = extractSystemFilePackages(resolvedSystemPath);
|
|
586
355
|
packageMap = {};
|
|
587
356
|
for (const specifier of packageSpecifiers) try {
|
|
588
357
|
const resolved = await this.resolve(specifier);
|
|
@@ -686,7 +455,7 @@ function animusExtract(options) {
|
|
|
686
455
|
if (id === RESOLVED_BRIDGE_ID) return `
|
|
687
456
|
import css from '${VIRTUAL_COMPONENTS_ID}';
|
|
688
457
|
|
|
689
|
-
const GLOBAL_KEY = '
|
|
458
|
+
const GLOBAL_KEY = '__animus_sheet_${createHash("md5").update(options.system).digest("hex").slice(0, 8)}__';
|
|
690
459
|
let sheet = globalThis[GLOBAL_KEY] || null;
|
|
691
460
|
|
|
692
461
|
if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
|
|
@@ -858,4 +627,4 @@ if (import.meta.hot) {
|
|
|
858
627
|
};
|
|
859
628
|
}
|
|
860
629
|
//#endregion
|
|
861
|
-
export { animusExtract, animusExtract as default
|
|
630
|
+
export { animusExtract, animusExtract as default };
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standalone script executed via `bun run` to resolve global styles.
|
|
3
3
|
*
|
|
4
|
-
* Usage: bun run resolve-global-styles.ts <system-path> <theme-json> <output-file>
|
|
4
|
+
* Usage: bun run resolve-global-styles.ts <system-path> <theme-json-path> <output-file>
|
|
5
5
|
*
|
|
6
|
-
* Imports the system module
|
|
7
|
-
*
|
|
8
|
-
* prop config + theme + transforms, and writes the resolved CSS to output.
|
|
9
|
-
*
|
|
10
|
-
* @keyframes blocks are serialized as raw CSS (no prop resolution needed).
|
|
6
|
+
* Imports the system module for live transform functions (ESM isolation),
|
|
7
|
+
* then delegates all resolution logic to @animus-ui/extract/pipeline.
|
|
11
8
|
*/
|
|
12
9
|
export {};
|
|
13
10
|
//# sourceMappingURL=resolve-global-styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-global-styles.d.ts","sourceRoot":"","sources":["../src/resolve-global-styles.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve-global-styles.d.ts","sourceRoot":"","sources":["../src/resolve-global-styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkEH,OAAO,EAAE,CAAC"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { resolveGlobalStyles } from "@animus-ui/extract/pipeline";
|
|
2
3
|
//#region src/resolve-global-styles.ts
|
|
3
4
|
/**
|
|
4
5
|
* Standalone script executed via `bun run` to resolve global styles.
|
|
5
6
|
*
|
|
6
|
-
* Usage: bun run resolve-global-styles.ts <system-path> <theme-json> <output-file>
|
|
7
|
+
* Usage: bun run resolve-global-styles.ts <system-path> <theme-json-path> <output-file>
|
|
7
8
|
*
|
|
8
|
-
* Imports the system module
|
|
9
|
-
*
|
|
10
|
-
* prop config + theme + transforms, and writes the resolved CSS to output.
|
|
11
|
-
*
|
|
12
|
-
* @keyframes blocks are serialized as raw CSS (no prop resolution needed).
|
|
9
|
+
* Imports the system module for live transform functions (ESM isolation),
|
|
10
|
+
* then delegates all resolution logic to @animus-ui/extract/pipeline.
|
|
13
11
|
*/
|
|
14
12
|
const [systemPath, themeJsonPath, outputFile] = process.argv.slice(2);
|
|
15
13
|
if (!systemPath || !themeJsonPath || !outputFile) {
|
|
@@ -18,90 +16,16 @@ if (!systemPath || !themeJsonPath || !outputFile) {
|
|
|
18
16
|
}
|
|
19
17
|
const mod = await import(systemPath);
|
|
20
18
|
const ds = mod.ds || mod.default || mod.system;
|
|
21
|
-
if (!ds || !ds.
|
|
22
|
-
const cfg = ds.
|
|
19
|
+
if (!ds || !ds.toConfig) throw new Error("Module does not export a SystemInstance with .serialize()");
|
|
20
|
+
const cfg = ds.toConfig();
|
|
23
21
|
const flat = JSON.parse(readFileSync(themeJsonPath, "utf-8"));
|
|
24
22
|
const propConfig = JSON.parse(cfg.propConfig);
|
|
25
23
|
const transforms = cfg.transforms || {};
|
|
26
|
-
const
|
|
24
|
+
const globalStyleBlocks = {};
|
|
25
|
+
for (const [key, val] of Object.entries(mod)) if (val && typeof val === "object" && val.__brand === "GlobalStyleBlock") globalStyleBlocks[key] = val.styles;
|
|
27
26
|
const variableMap = {};
|
|
28
27
|
for (const [tokenPath, value] of Object.entries(flat)) if (typeof value === "string" && value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
|
|
29
|
-
|
|
30
|
-
if (s.startsWith("Webkit")) return "-webkit-" + camelToKebab(s.slice(6));
|
|
31
|
-
return s.replace(/[A-Z]/g, (m, i) => (i > 0 ? "-" : "") + m.toLowerCase());
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Resolve {scale.path} and {scale.path/alpha} token aliases in a CSS value string.
|
|
35
|
-
* Mirrors the Rust theme_resolver's resolve_token_aliases logic.
|
|
36
|
-
*/
|
|
37
|
-
function resolveTokenAliases(value) {
|
|
38
|
-
if (!value.includes("{")) return value;
|
|
39
|
-
return value.replace(/\{([^}]+)\}/g, (_match, content) => {
|
|
40
|
-
const slashIdx = content.indexOf("/");
|
|
41
|
-
const tokenPath = slashIdx >= 0 ? content.slice(0, slashIdx) : content;
|
|
42
|
-
const alpha = slashIdx >= 0 ? parseInt(content.slice(slashIdx + 1), 10) : null;
|
|
43
|
-
const dotIdx = tokenPath.indexOf(".");
|
|
44
|
-
const flatKey = dotIdx >= 0 ? tokenPath.slice(0, dotIdx) + "." + tokenPath.slice(dotIdx + 1).replace(/\./g, "-") : tokenPath;
|
|
45
|
-
let resolved;
|
|
46
|
-
if (variableMap[flatKey]) resolved = `var(${variableMap[flatKey]})`;
|
|
47
|
-
else if (flat[flatKey] != null) resolved = flat[flatKey];
|
|
48
|
-
else return `{${content}}`;
|
|
49
|
-
if (alpha === 0) return "transparent";
|
|
50
|
-
if (alpha != null && alpha !== 100) return `color-mix(in srgb, ${resolved} ${alpha}%, transparent)`;
|
|
51
|
-
return resolved;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Resolve a block of selectors → style objects using prop config + theme.
|
|
56
|
-
* @keyframes selectors are delegated to serializeRawBlock.
|
|
57
|
-
*/
|
|
58
|
-
function resolveBlock(selectors) {
|
|
59
|
-
const rules = [];
|
|
60
|
-
for (const [selector, styleObj] of Object.entries(selectors)) {
|
|
61
|
-
if (selector.startsWith("@keyframes")) {
|
|
62
|
-
const frames = [];
|
|
63
|
-
for (const [pct, frameStyles] of Object.entries(styleObj)) if (typeof frameStyles === "object" && frameStyles !== null) {
|
|
64
|
-
const decls = [];
|
|
65
|
-
for (const [prop, raw] of Object.entries(frameStyles)) {
|
|
66
|
-
const cfg = propConfig[prop];
|
|
67
|
-
const cssProps = cfg?.properties?.length ? cfg.properties : cfg ? [cfg.property] : [prop];
|
|
68
|
-
let resolved = raw;
|
|
69
|
-
if (cfg?.scale) {
|
|
70
|
-
const key = cfg.scale + "." + raw;
|
|
71
|
-
if (flat[key] != null) resolved = flat[key];
|
|
72
|
-
}
|
|
73
|
-
resolved = resolveTokenAliases(String(resolved));
|
|
74
|
-
for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
|
|
75
|
-
}
|
|
76
|
-
frames.push(` ${pct} {\n${decls.join("\n")}\n }`);
|
|
77
|
-
}
|
|
78
|
-
rules.push(`${selector} {\n${frames.join("\n")}\n}`);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
const decls = [];
|
|
82
|
-
for (const [prop, raw] of Object.entries(styleObj)) {
|
|
83
|
-
const config = propConfig[prop];
|
|
84
|
-
const cssProps = config?.properties?.length ? config.properties : config ? [config.property] : [prop];
|
|
85
|
-
let resolved = raw;
|
|
86
|
-
if (config?.scale) {
|
|
87
|
-
const key = config.scale + "." + raw;
|
|
88
|
-
if (flat[key] != null) resolved = flat[key];
|
|
89
|
-
}
|
|
90
|
-
if (config?.transform && transforms[config.transform]) {
|
|
91
|
-
const fn = transforms[config.transform];
|
|
92
|
-
const input = typeof resolved === "string" && !isNaN(Number(resolved)) ? Number(resolved) : resolved;
|
|
93
|
-
resolved = String(fn(input));
|
|
94
|
-
} else resolved = String(resolved);
|
|
95
|
-
resolved = resolveTokenAliases(resolved);
|
|
96
|
-
for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
|
|
97
|
-
}
|
|
98
|
-
if (decls.length) rules.push(`${selector} {\n${decls.join("\n")}\n}`);
|
|
99
|
-
}
|
|
100
|
-
return rules.join("\n\n");
|
|
101
|
-
}
|
|
102
|
-
const result = {};
|
|
103
|
-
if (gs.reset) result.reset = resolveBlock(gs.reset);
|
|
104
|
-
if (gs.global) result.global = resolveBlock(gs.global);
|
|
28
|
+
const result = resolveGlobalStyles(globalStyleBlocks, propConfig, flat, variableMap, transforms);
|
|
105
29
|
writeFileSync(outputFile, JSON.stringify(result));
|
|
106
30
|
//#endregion
|
|
107
31
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animus-ui/vite-plugin",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.30",
|
|
4
4
|
"description": "Animus static CSS extraction Vite plugin",
|
|
5
5
|
"author": "codecaaron <airrobb@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"vite": ">=5.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@animus-ui/extract": "0.1.0-next.
|
|
28
|
+
"@animus-ui/extract": "0.1.0-next.30",
|
|
29
|
+
"@animus-ui/system": "0.1.0-next.30",
|
|
29
30
|
"browserslist": "^4.24.0",
|
|
30
31
|
"lightningcss": "^1.29.0"
|
|
31
32
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Evaluate a theme object that has already been loaded/imported.
|
|
3
|
-
*
|
|
4
|
-
* If the theme has a `.manifest` property (from ThemeBuilder.build()),
|
|
5
|
-
* reads structured data directly — no re-flattening or var() pattern-matching.
|
|
6
|
-
* Falls back to legacy path for themes without manifests.
|
|
7
|
-
*/
|
|
8
|
-
export declare function evaluateThemeObject(theme: Record<string, any>): {
|
|
9
|
-
scalesJson: string;
|
|
10
|
-
variableMapJson: string;
|
|
11
|
-
variableCss: string;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Evaluate a theme module and flatten all scales to a JSON map.
|
|
15
|
-
* Format: { "scale_name.key": "css_value" }
|
|
16
|
-
*
|
|
17
|
-
* Uses Vite's ssrLoadModule to evaluate the theme at build start,
|
|
18
|
-
* so all computed scales (shadows, gradients that depend on colors)
|
|
19
|
-
* are resolved to their final values.
|
|
20
|
-
*/
|
|
21
|
-
export declare function evaluateTheme(ssrLoadModule: (url: string) => Promise<Record<string, any>>, themePath: string): Promise<{
|
|
22
|
-
scalesJson: string;
|
|
23
|
-
variableMapJson: string;
|
|
24
|
-
variableCss: string;
|
|
25
|
-
}>;
|
|
26
|
-
/**
|
|
27
|
-
* Resolve global style objects to a CSS string.
|
|
28
|
-
*
|
|
29
|
-
* Each key in `globalStyles` is a CSS selector, each value is a style object
|
|
30
|
-
* using the same prop shorthand as component `.styles()` blocks.
|
|
31
|
-
*/
|
|
32
|
-
export declare function resolveGlobalStyles(globalStyles: Record<string, Record<string, any>>, propConfigJson: string, flat: Record<string, string>, transforms: Record<string, (v: any) => any>): string;
|
|
33
|
-
//# sourceMappingURL=theme-evaluator.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme-evaluator.d.ts","sourceRoot":"","sources":["../src/theme-evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB,CAiBA;AA0CD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAC5D,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IACT,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAWD;AAuKD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EACjD,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC,GAC1C,MAAM,CAiCR"}
|