@animus-ui/vite-plugin 0.1.0-next.v7 → 0.1.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/README.md +41 -0
- package/dist/index.d.ts +45 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +426 -491
- package/package.json +33 -14
- package/dist/config-serializer.d.ts +0 -25
- package/dist/config-serializer.d.ts.map +0 -1
- package/dist/resolve-global-styles.d.ts +0 -13
- package/dist/resolve-global-styles.d.ts.map +0 -1
- package/dist/resolve-transforms.d.ts +0 -14
- package/dist/resolve-transforms.d.ts.map +0 -1
- package/dist/theme-evaluator.d.ts +0 -32
- package/dist/theme-evaluator.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,136 +1,29 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_EXTENSIONS, applyUnitFallback, assembleStylesheet, extractSystemFilePackages, preprocessMdx, stripLeadingLayerDeclaration, validateLayerOrder } from "@animus-ui/extract/pipeline";
|
|
3
|
+
import browserslist from "browserslist";
|
|
3
4
|
import { createHash } from "crypto";
|
|
4
|
-
import { existsSync, readFileSync, readdirSync, statSync
|
|
5
|
-
import {
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
6
|
+
import { browserslistToTargets, transform } from "lightningcss";
|
|
6
7
|
import { dirname, extname, join, relative, resolve } from "path";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
8
|
//#region \0rolldown/runtime.js
|
|
9
9
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
10
10
|
//#endregion
|
|
11
|
-
//#region src/theme-evaluator.ts
|
|
12
|
-
/**
|
|
13
|
-
* Evaluate a theme object that has already been loaded/imported.
|
|
14
|
-
*
|
|
15
|
-
* This is the pure logic: takes a theme object, flattens scales, builds variable CSS.
|
|
16
|
-
* Use this when you have already loaded the module yourself.
|
|
17
|
-
*/
|
|
18
|
-
function evaluateThemeObject(theme) {
|
|
19
|
-
const flat = {};
|
|
20
|
-
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
21
|
-
if (scaleName.startsWith("_")) continue;
|
|
22
|
-
if (scaleName === "breakpoints") {
|
|
23
|
-
flattenScale(flat, "breakpoints", scaleValue);
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
if (typeof scaleValue === "object" && scaleValue !== null && !Array.isArray(scaleValue)) flattenScale(flat, scaleName, scaleValue);
|
|
27
|
-
}
|
|
28
|
-
const variableMap = {};
|
|
29
|
-
for (const [tokenPath, value] of Object.entries(flat)) if (value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
|
|
30
|
-
const variableCss = buildVariableCss(theme);
|
|
31
|
-
return {
|
|
32
|
-
scalesJson: JSON.stringify(flat),
|
|
33
|
-
variableMapJson: JSON.stringify(variableMap),
|
|
34
|
-
variableCss
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Evaluate a theme module and flatten all scales to a JSON map.
|
|
39
|
-
* Format: { "scale_name.key": "css_value" }
|
|
40
|
-
*
|
|
41
|
-
* Uses Vite's ssrLoadModule to evaluate the theme at build start,
|
|
42
|
-
* so all computed scales (shadows, gradients that depend on colors)
|
|
43
|
-
* are resolved to their final values.
|
|
44
|
-
*/
|
|
45
|
-
async function evaluateTheme(ssrLoadModule, themePath) {
|
|
46
|
-
const mod = await ssrLoadModule(themePath);
|
|
47
|
-
const theme = mod.theme || mod.default;
|
|
48
|
-
if (!theme) throw new Error(`Theme module at ${themePath} must export 'theme' or a default export`);
|
|
49
|
-
return evaluateThemeObject(theme);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Build the full CSS variable block from a theme's _variables and _tokens.
|
|
53
|
-
*
|
|
54
|
-
* Produces:
|
|
55
|
-
* :root { --color-navy-500: #282a36; ... }
|
|
56
|
-
* [data-color-mode="dark"] { --color-primary: #ff80bf; ... }
|
|
57
|
-
*/
|
|
58
|
-
function buildVariableCss(theme) {
|
|
59
|
-
const parts = [];
|
|
60
|
-
if (theme._variables != null && typeof theme._variables === "object") {
|
|
61
|
-
const rootLines = [];
|
|
62
|
-
for (const categoryValue of Object.values(theme._variables)) {
|
|
63
|
-
if (categoryValue == null || typeof categoryValue !== "object") continue;
|
|
64
|
-
for (const [cssVar, cssValue] of Object.entries(categoryValue)) if (typeof cssValue === "string") rootLines.push(` ${cssVar}: ${cssValue};`);
|
|
65
|
-
}
|
|
66
|
-
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
67
|
-
}
|
|
68
|
-
if (theme._tokens?.modes != null && typeof theme._tokens.modes === "object") {
|
|
69
|
-
const defaultMode = typeof theme.mode === "string" ? theme.mode : Object.keys(theme._tokens.modes)[0];
|
|
70
|
-
for (const [modeName, modeTokens] of Object.entries(theme._tokens.modes)) {
|
|
71
|
-
if (modeName === defaultMode) continue;
|
|
72
|
-
if (modeTokens == null || typeof modeTokens !== "object") continue;
|
|
73
|
-
const modeLines = [];
|
|
74
|
-
flattenModeTokens(modeLines, modeTokens, "");
|
|
75
|
-
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return parts.join("\n\n");
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Recursively flatten mode token objects into CSS variable declarations.
|
|
82
|
-
*
|
|
83
|
-
* Examples:
|
|
84
|
-
* { primary: '#ff80bf' } → ' --color-primary: #ff80bf;'
|
|
85
|
-
* { background: { _: '#fff', muted: '#f5f5f5' } }
|
|
86
|
-
* → ' --color-background: #fff;'
|
|
87
|
-
* ' --color-background-muted: #f5f5f5;'
|
|
88
|
-
*
|
|
89
|
-
* The special key `_` maps to the base name (no suffix).
|
|
90
|
-
*/
|
|
91
|
-
function flattenModeTokens(lines, obj, prefix) {
|
|
92
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
93
|
-
let namePart;
|
|
94
|
-
if (key === "_") namePart = prefix;
|
|
95
|
-
else namePart = prefix ? `${prefix}-${key}` : key;
|
|
96
|
-
if (typeof value === "string" || typeof value === "number") lines.push(` --color-${namePart}: ${value};`);
|
|
97
|
-
else if (value != null && typeof value === "object" && !Array.isArray(value)) flattenModeTokens(lines, value, namePart);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function flattenScale(flat, prefix, obj, parentKey = "") {
|
|
101
|
-
if (typeof obj !== "object" || obj === null) {
|
|
102
|
-
const key = parentKey ? `${prefix}.${parentKey}` : prefix;
|
|
103
|
-
flat[key] = String(obj);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
107
|
-
const fullKey = parentKey ? `${parentKey}-${key}` : key;
|
|
108
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenScale(flat, prefix, value, fullKey);
|
|
109
|
-
else flat[`${prefix}.${fullKey}`] = String(value);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
113
11
|
//#region src/index.ts
|
|
114
|
-
const __pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
115
12
|
const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
|
|
116
13
|
const RESOLVED_CSS_ID = "\0virtual:animus/styles.css";
|
|
117
14
|
const VIRTUAL_COMPONENTS_ID = "virtual:animus/components.js";
|
|
118
15
|
const RESOLVED_COMPONENTS_ID = "\0virtual:animus/components.js";
|
|
119
16
|
const VIRTUAL_BRIDGE_ID = "virtual:animus/hmr-bridge.js";
|
|
120
17
|
const RESOLVED_BRIDGE_ID = "\0virtual:animus/hmr-bridge.js";
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
".tsx",
|
|
124
|
-
".js",
|
|
125
|
-
".jsx"
|
|
126
|
-
]);
|
|
18
|
+
const VIRTUAL_SYSTEM_PROPS_ID = "virtual:animus/system-props";
|
|
19
|
+
const RESOLVED_SYSTEM_PROPS_ID = "\0virtual:animus/system-props";
|
|
127
20
|
const DEFAULT_EXCLUDE = [
|
|
128
21
|
"node_modules",
|
|
129
22
|
"dist",
|
|
130
23
|
".test.",
|
|
131
24
|
".spec."
|
|
132
25
|
];
|
|
133
|
-
function discoverFiles(dir, rootDir, excludePatterns) {
|
|
26
|
+
function discoverFiles(dir, rootDir, excludePatterns, extensionsSet) {
|
|
134
27
|
const results = [];
|
|
135
28
|
let entries;
|
|
136
29
|
try {
|
|
@@ -148,115 +41,42 @@ function discoverFiles(dir, rootDir, excludePatterns) {
|
|
|
148
41
|
} catch {
|
|
149
42
|
continue;
|
|
150
43
|
}
|
|
151
|
-
if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns));
|
|
152
|
-
else if (
|
|
44
|
+
if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns, extensionsSet));
|
|
45
|
+
else if (extensionsSet.has(extname(entry))) results.push(fullPath);
|
|
153
46
|
}
|
|
154
47
|
return results;
|
|
155
48
|
}
|
|
156
49
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* Rust emits `__TRANSFORM__name__rawValue__` for props with transforms.
|
|
160
|
-
* This function resolves each placeholder using the actual JS transform
|
|
161
|
-
* functions from the config.
|
|
50
|
+
* Resolve browser targets for Lightning CSS.
|
|
51
|
+
* Priority: explicit config → project browserslist → 'defaults' fallback.
|
|
162
52
|
*/
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return typeof result === "object" ? JSON.stringify(result) : String(result);
|
|
172
|
-
});
|
|
53
|
+
function resolveLightningTargets(explicitTargets, rootDir) {
|
|
54
|
+
let queries;
|
|
55
|
+
if (explicitTargets) queries = Array.isArray(explicitTargets) ? explicitTargets : [explicitTargets];
|
|
56
|
+
else {
|
|
57
|
+
const detected = browserslist(void 0, { path: rootDir });
|
|
58
|
+
queries = detected.length > 0 ? detected : browserslist("defaults");
|
|
59
|
+
}
|
|
60
|
+
return browserslistToTargets(Array.isArray(queries) && typeof queries[0] === "string" && queries[0].includes(" ") ? browserslist(queries) : queries);
|
|
173
61
|
}
|
|
174
62
|
/**
|
|
175
|
-
* CSS
|
|
176
|
-
*
|
|
177
|
-
* Bare numerics on properties NOT in this set receive `px`.
|
|
63
|
+
* Post-process CSS with Lightning CSS: autoprefixing + optional minification.
|
|
64
|
+
* On failure, returns the original CSS and logs a warning.
|
|
178
65
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"flex-negative",
|
|
194
|
-
"flex-order",
|
|
195
|
-
"grid-area",
|
|
196
|
-
"grid-row",
|
|
197
|
-
"grid-row-end",
|
|
198
|
-
"grid-row-span",
|
|
199
|
-
"grid-row-start",
|
|
200
|
-
"grid-column",
|
|
201
|
-
"grid-column-end",
|
|
202
|
-
"grid-column-span",
|
|
203
|
-
"grid-column-start",
|
|
204
|
-
"font-weight",
|
|
205
|
-
"line-clamp",
|
|
206
|
-
"line-height",
|
|
207
|
-
"opacity",
|
|
208
|
-
"order",
|
|
209
|
-
"orphans",
|
|
210
|
-
"tab-size",
|
|
211
|
-
"widows",
|
|
212
|
-
"z-index",
|
|
213
|
-
"zoom",
|
|
214
|
-
"fill-opacity",
|
|
215
|
-
"flood-opacity",
|
|
216
|
-
"stop-opacity",
|
|
217
|
-
"stroke-dasharray",
|
|
218
|
-
"stroke-dashoffset",
|
|
219
|
-
"stroke-miterlimit",
|
|
220
|
-
"stroke-opacity",
|
|
221
|
-
"stroke-width"
|
|
222
|
-
]);
|
|
223
|
-
/**
|
|
224
|
-
* Append `px` to bare numeric values in CSS declarations for properties
|
|
225
|
-
* that expect length units. Unitless properties are preserved as-is.
|
|
226
|
-
* Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
|
|
227
|
-
*/
|
|
228
|
-
function applyUnitFallback(css) {
|
|
229
|
-
return css.replace(/([a-z-]+)\s*:\s*([^;]+);/g, (match, prop, value) => {
|
|
230
|
-
if (UNITLESS_PROPERTIES.has(prop)) return match;
|
|
231
|
-
let depth = 0;
|
|
232
|
-
let fixed = "";
|
|
233
|
-
let i = 0;
|
|
234
|
-
while (i < value.length) if (value[i] === "(") {
|
|
235
|
-
depth++;
|
|
236
|
-
fixed += value[i];
|
|
237
|
-
i++;
|
|
238
|
-
} else if (value[i] === ")") {
|
|
239
|
-
depth--;
|
|
240
|
-
fixed += value[i];
|
|
241
|
-
i++;
|
|
242
|
-
} else if (depth > 0) {
|
|
243
|
-
fixed += value[i];
|
|
244
|
-
i++;
|
|
245
|
-
} else {
|
|
246
|
-
const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
|
|
247
|
-
if (numMatch) {
|
|
248
|
-
const num = numMatch[1];
|
|
249
|
-
const after = value[i + num.length];
|
|
250
|
-
if (after && /[a-z%]/i.test(after)) fixed += num;
|
|
251
|
-
else fixed += num + "px";
|
|
252
|
-
i += num.length;
|
|
253
|
-
} else {
|
|
254
|
-
fixed += value[i];
|
|
255
|
-
i++;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return fixed !== value ? `${prop}:${fixed};` : match;
|
|
259
|
-
});
|
|
66
|
+
function postProcessCss(css, opts) {
|
|
67
|
+
if (!css) return css;
|
|
68
|
+
try {
|
|
69
|
+
return transform({
|
|
70
|
+
filename: "animus-extracted.css",
|
|
71
|
+
code: Buffer.from(css),
|
|
72
|
+
minify: opts.minify,
|
|
73
|
+
targets: opts.targets
|
|
74
|
+
}).code.toString();
|
|
75
|
+
} catch (e) {
|
|
76
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
77
|
+
(opts.warnFn ?? console.warn)(`[animus] Lightning CSS post-processing failed: ${msg}`);
|
|
78
|
+
return css;
|
|
79
|
+
}
|
|
260
80
|
}
|
|
261
81
|
/** Compute MD5 content hash for a string. */
|
|
262
82
|
function contentHash(source) {
|
|
@@ -277,44 +97,14 @@ function buildFileEntriesFromCache(cache, changedPath) {
|
|
|
277
97
|
});
|
|
278
98
|
return entries;
|
|
279
99
|
}
|
|
280
|
-
|
|
281
|
-
* Resolve transform placeholders in CSS via bun subprocess.
|
|
282
|
-
* Falls back to in-process resolution if the subprocess fails.
|
|
283
|
-
*/
|
|
284
|
-
function resolveTransformPlaceholders(css, rootDir, configPath, transformRegistry) {
|
|
285
|
-
if (!css.includes("__TRANSFORM__")) return css;
|
|
286
|
-
try {
|
|
287
|
-
const ts = Date.now();
|
|
288
|
-
const tmpCss = join(tmpdir(), `animus-transforms-${ts}.css`);
|
|
289
|
-
const tmpOut = join(tmpdir(), `animus-transforms-${ts}.out.css`);
|
|
290
|
-
writeFileSync(tmpCss, css);
|
|
291
|
-
const candidates = [join(__pluginDir, "resolve-transforms.ts"), join(__pluginDir, "..", "src", "resolve-transforms.ts")];
|
|
292
|
-
try {
|
|
293
|
-
const pkgDir = dirname(__require.resolve("@animus-ui/vite-plugin/package.json", { paths: [rootDir] }));
|
|
294
|
-
candidates.push(join(pkgDir, "src", "resolve-transforms.ts"));
|
|
295
|
-
} catch {}
|
|
296
|
-
const scriptPath = candidates.find((p) => existsSync(p));
|
|
297
|
-
if (!scriptPath) throw new Error(`resolve-transforms.ts not found in: ${candidates.join(", ")}`);
|
|
298
|
-
execSync(`bun run "${scriptPath}" "${tmpCss}" "${tmpOut}" ${configPath ? `"${resolve(rootDir, configPath)}"` : ""}`, {
|
|
299
|
-
cwd: rootDir,
|
|
300
|
-
encoding: "utf-8"
|
|
301
|
-
});
|
|
302
|
-
const resolved = readFileSync(tmpOut, "utf-8");
|
|
303
|
-
try {
|
|
304
|
-
unlinkSync(tmpCss);
|
|
305
|
-
unlinkSync(tmpOut);
|
|
306
|
-
} catch {}
|
|
307
|
-
return resolved;
|
|
308
|
-
} catch (e) {
|
|
309
|
-
console.error("[animus-extract] Transform resolution failed:", e?.message || e);
|
|
310
|
-
return applyTransformPlaceholders(css, transformRegistry);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function animusExtract(options = {}) {
|
|
100
|
+
function animusExtract(options) {
|
|
314
101
|
let themeJson = "{}";
|
|
315
102
|
let variableMapJson = "{}";
|
|
103
|
+
let contextualVarsJson = "{}";
|
|
316
104
|
let configJson = "{}";
|
|
317
105
|
let groupRegistryJson = "{}";
|
|
106
|
+
let selectorAliasesJson = null;
|
|
107
|
+
let selectorOrderJson = null;
|
|
318
108
|
let isProd = false;
|
|
319
109
|
let rootDir = "";
|
|
320
110
|
const verbose = options.verbose || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
|
|
@@ -325,218 +115,205 @@ function animusExtract(options = {}) {
|
|
|
325
115
|
function warn(msg) {
|
|
326
116
|
(logger ?? console).warn(`[animus] ${msg}`);
|
|
327
117
|
}
|
|
118
|
+
function logTimingWaterfall(timing) {
|
|
119
|
+
if (!verbose) return;
|
|
120
|
+
for (const [key, label] of [
|
|
121
|
+
["parseAndWalk", "parse+walk"],
|
|
122
|
+
["importResolution", "imports"],
|
|
123
|
+
["extensionProvenance", "provenance"],
|
|
124
|
+
["topologicalSort", "topo-sort"],
|
|
125
|
+
["chainEvaluation", "chains"],
|
|
126
|
+
["jsxScanning", "jsx-scan"],
|
|
127
|
+
["systemPropAggregation", "sys-props"],
|
|
128
|
+
["usageLedger", "usage"],
|
|
129
|
+
["reconciliation", "reconcile"],
|
|
130
|
+
["cssGeneration", "css-gen"],
|
|
131
|
+
["manifestSerialization", "serialize"]
|
|
132
|
+
]) {
|
|
133
|
+
const ms = timing[key] ?? 0;
|
|
134
|
+
const pad = " ".repeat(Math.max(0, 15 - label.length));
|
|
135
|
+
const extra = key === "parseAndWalk" ? ` (${timing.fileCount ?? 0} files, ${timing.cacheHits ?? 0} cached)` : "";
|
|
136
|
+
log(` ${label}${pad}${String(ms).padStart(5)}ms${extra}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let lcssTargets = {};
|
|
328
140
|
let variableCss = "";
|
|
329
141
|
let globalCss = "";
|
|
330
|
-
let
|
|
142
|
+
let globalStyleBlocksJson = null;
|
|
143
|
+
let keyframesBlocksJson = null;
|
|
144
|
+
let pathAliasesJson = null;
|
|
145
|
+
let extensionsSet = new Set(options.extensions ?? DEFAULT_EXTENSIONS);
|
|
331
146
|
let storedManifest = null;
|
|
332
147
|
let storedManifestJson = "";
|
|
333
148
|
let resolvedComponentCss = "";
|
|
334
149
|
let storedSheets = null;
|
|
150
|
+
let layerDeclaration = "";
|
|
151
|
+
let fragmentCache = /* @__PURE__ */ new Map();
|
|
152
|
+
let reverseProvenance = {};
|
|
153
|
+
let storedSystemPropMapJson = "{}";
|
|
154
|
+
let storedDynamicPropsJson = "{}";
|
|
155
|
+
let storedTransformsSource = "{}";
|
|
335
156
|
const fileCache = /* @__PURE__ */ new Map();
|
|
336
157
|
let packageMap = {};
|
|
158
|
+
let externalPackageDirs = [];
|
|
159
|
+
const externalSourceEntries = /* @__PURE__ */ new Map();
|
|
160
|
+
let devServer;
|
|
337
161
|
let bridgeInjected = false;
|
|
338
|
-
let resolvedConfigPath = null;
|
|
339
|
-
let resolvedThemePath = null;
|
|
340
162
|
let resolvedSystemPath = null;
|
|
341
|
-
/** Resolve config via bun subprocess. Updates configJson + groupRegistryJson. */
|
|
342
|
-
function loadConfig() {
|
|
343
|
-
if (options.config) {
|
|
344
|
-
configJson = options.config;
|
|
345
|
-
groupRegistryJson = options.groupRegistry ?? groupRegistryJson;
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const configSource = options.configPath ? resolve(rootDir, options.configPath) : "@animus-ui/core";
|
|
349
|
-
const requireExpr = options.configPath ? `require('${configSource.replace(/\\/g, "/")}')` : `require('@animus-ui/core')`;
|
|
350
|
-
if (options.configPath) resolvedConfigPath = resolve(rootDir, options.configPath);
|
|
351
|
-
try {
|
|
352
|
-
const result = execSync(`bun -e "const m = ${requireExpr}; const r = m.getExtractConfig(); process.stdout.write(JSON.stringify(r))"`, {
|
|
353
|
-
cwd: rootDir,
|
|
354
|
-
encoding: "utf-8"
|
|
355
|
-
});
|
|
356
|
-
const { propConfig, groupRegistry } = JSON.parse(result);
|
|
357
|
-
configJson = propConfig;
|
|
358
|
-
groupRegistryJson = groupRegistry;
|
|
359
|
-
} catch (e) {
|
|
360
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to auto-import config from ${configSource}: ${e}`);
|
|
361
|
-
console.warn(`[animus-extract] Failed to auto-import config from ${configSource}:`, e);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
/** Resolve theme via bun subprocess. Updates themeJson + variableCss. */
|
|
365
|
-
function loadTheme() {
|
|
366
|
-
if (options.theme) {
|
|
367
|
-
if (typeof options.theme === "string") {
|
|
368
|
-
themeJson = options.theme;
|
|
369
|
-
variableCss = "";
|
|
370
|
-
} else {
|
|
371
|
-
themeJson = options.theme.scales;
|
|
372
|
-
variableCss = options.theme.variables;
|
|
373
|
-
}
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
resolvedThemePath = null;
|
|
377
|
-
if (options.themePath) {
|
|
378
|
-
const p = resolve(rootDir, options.themePath);
|
|
379
|
-
if (existsSync(p)) resolvedThemePath = p;
|
|
380
|
-
else if (options.strict) throw new Error(`[animus-extract] Theme file not found: ${p}`);
|
|
381
|
-
else console.warn(`[animus-extract] Theme file not found: ${p}`);
|
|
382
|
-
} else {
|
|
383
|
-
const candidates = [
|
|
384
|
-
join(rootDir, "src", "theme.ts"),
|
|
385
|
-
join(rootDir, "src", "theme.js"),
|
|
386
|
-
join(rootDir, "theme.ts"),
|
|
387
|
-
join(rootDir, "theme.js")
|
|
388
|
-
];
|
|
389
|
-
for (const candidate of candidates) if (existsSync(candidate)) {
|
|
390
|
-
resolvedThemePath = candidate;
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (resolvedThemePath) try {
|
|
395
|
-
const themeJson_ = execSync(`bun -e "${[
|
|
396
|
-
`const m = require(${JSON.stringify(resolvedThemePath)});`,
|
|
397
|
-
`const theme = m.theme || m.default;`,
|
|
398
|
-
`process.stdout.write(JSON.stringify(theme || {}));`
|
|
399
|
-
].join(" ").replace(/"/g, "\\\"")}"`, {
|
|
400
|
-
cwd: rootDir,
|
|
401
|
-
encoding: "utf-8"
|
|
402
|
-
});
|
|
403
|
-
const theme = JSON.parse(themeJson_);
|
|
404
|
-
if (theme && Object.keys(theme).length > 0) {
|
|
405
|
-
const result = evaluateThemeObject(theme);
|
|
406
|
-
themeJson = result.scalesJson;
|
|
407
|
-
variableMapJson = result.variableMapJson;
|
|
408
|
-
variableCss = result.variableCss;
|
|
409
|
-
}
|
|
410
|
-
} catch (e) {
|
|
411
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to load theme from ${resolvedThemePath}: ${e}`);
|
|
412
|
-
console.warn(`[animus-extract] Failed to load theme from ${resolvedThemePath}:`, e);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
163
|
/**
|
|
416
|
-
* Load a SystemInstance via
|
|
417
|
-
*
|
|
418
|
-
*
|
|
164
|
+
* Load a SystemInstance via Rust NAPI (rquickjs bundled eval).
|
|
165
|
+
* The Rust crate reads the system file, strips TS types via OXC, resolves
|
|
166
|
+
* workspace package dependencies, bundles all modules, evaluates with
|
|
167
|
+
* rquickjs, and returns serialized config directly — no subprocess needed.
|
|
419
168
|
*/
|
|
420
169
|
function loadSystem() {
|
|
421
|
-
if (!options.system) return;
|
|
422
170
|
resolvedSystemPath = resolve(rootDir, options.system);
|
|
423
171
|
try {
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
} catch {}
|
|
437
|
-
const parsed = JSON.parse(result);
|
|
438
|
-
configJson = parsed.propConfig;
|
|
439
|
-
groupRegistryJson = parsed.groupRegistry;
|
|
440
|
-
if (parsed.tokens && Object.keys(parsed.tokens).length > 0) {
|
|
441
|
-
const result = evaluateThemeObject(parsed.tokens);
|
|
442
|
-
themeJson = result.scalesJson;
|
|
443
|
-
variableMapJson = result.variableMapJson;
|
|
444
|
-
variableCss = result.variableCss;
|
|
445
|
-
}
|
|
446
|
-
if (parsed.globalStyles) {
|
|
447
|
-
const hasReset = parsed.globalStyles.reset && Object.keys(parsed.globalStyles.reset).length > 0;
|
|
448
|
-
const hasGlobal = parsed.globalStyles.global && Object.keys(parsed.globalStyles.global).length > 0;
|
|
449
|
-
if (hasReset || hasGlobal) try {
|
|
450
|
-
const gsTmp = Date.now();
|
|
451
|
-
const gsThemeFile = join(tmpdir(), `animus-gs-theme-${gsTmp}.json`);
|
|
452
|
-
const gsOut = join(tmpdir(), `animus-global-${gsTmp}.json`);
|
|
453
|
-
writeFileSync(gsThemeFile, themeJson);
|
|
454
|
-
const gsCandidates = [join(__pluginDir, "resolve-global-styles.ts"), join(__pluginDir, "..", "src", "resolve-global-styles.ts")];
|
|
455
|
-
try {
|
|
456
|
-
const pkgDir = dirname(__require.resolve("@animus-ui/vite-plugin/package.json", { paths: [rootDir] }));
|
|
457
|
-
gsCandidates.push(join(pkgDir, "src", "resolve-global-styles.ts"));
|
|
458
|
-
} catch {}
|
|
459
|
-
const gsScriptPath = gsCandidates.find((p) => existsSync(p));
|
|
460
|
-
if (!gsScriptPath) throw new Error(`resolve-global-styles.ts not found in: ${gsCandidates.join(", ")}`);
|
|
461
|
-
execSync(`bun run "${gsScriptPath}" "${resolvedSystemPath}" "${gsThemeFile}" "${gsOut}"`, {
|
|
462
|
-
cwd: rootDir,
|
|
463
|
-
encoding: "utf-8"
|
|
464
|
-
});
|
|
465
|
-
const gsResult = JSON.parse(readFileSync(gsOut, "utf-8"));
|
|
466
|
-
const parts = [gsResult.reset, gsResult.global].filter(Boolean);
|
|
467
|
-
if (parts.length > 0) globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
|
|
468
|
-
try {
|
|
469
|
-
unlinkSync(gsThemeFile);
|
|
470
|
-
unlinkSync(gsOut);
|
|
471
|
-
} catch {}
|
|
472
|
-
} catch (e) {
|
|
473
|
-
console.warn("[animus-extract] Global styles resolution failed:", e?.message || e);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (parsed.transformNames && parsed.transformNames.length > 0) {
|
|
477
|
-
const tsTmp = Date.now();
|
|
478
|
-
const tmpResolve = join(tmpdir(), `animus-transforms-resolve-${tsTmp}.js`);
|
|
479
|
-
writeFileSync(tmpResolve, `const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.serialize();\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`);
|
|
480
|
-
globalThis.__animus_system_resolve_script = tmpResolve;
|
|
481
|
-
}
|
|
172
|
+
const { loadSystemModule } = __require("@animus-ui/extract");
|
|
173
|
+
const config = loadSystemModule(resolvedSystemPath, rootDir);
|
|
174
|
+
configJson = config.propConfig;
|
|
175
|
+
groupRegistryJson = config.groupRegistry;
|
|
176
|
+
selectorAliasesJson = config.selectorAliases || null;
|
|
177
|
+
selectorOrderJson = config.selectorOrder || null;
|
|
178
|
+
themeJson = config.scalesJson;
|
|
179
|
+
variableMapJson = config.variableMapJson;
|
|
180
|
+
variableCss = config.variableCss;
|
|
181
|
+
contextualVarsJson = config.contextualVarsJson;
|
|
182
|
+
globalStyleBlocksJson = config.globalStyleBlocks || null;
|
|
183
|
+
keyframesBlocksJson = config.keyframesBlocks || null;
|
|
482
184
|
} catch (e) {
|
|
483
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to load system from ${resolvedSystemPath}: ${e}
|
|
185
|
+
if (options.strict) throw new Error(`[animus-extract] Failed to load system from ${resolvedSystemPath}: ${e}`, { cause: e });
|
|
484
186
|
console.warn(`[animus-extract] Failed to load system from ${resolvedSystemPath}:`, e);
|
|
485
187
|
}
|
|
486
188
|
}
|
|
487
189
|
/**
|
|
488
190
|
* Run project analysis and update the manifest.
|
|
489
|
-
*
|
|
191
|
+
*
|
|
192
|
+
* Pipeline: NAPI analyzeProject → unit fallback.
|
|
193
|
+
* Transforms are resolved in-process via boa_engine during Rust extraction.
|
|
194
|
+
* No subprocess needed.
|
|
490
195
|
*/
|
|
491
196
|
function runAnalysis(fileEntries) {
|
|
492
197
|
try {
|
|
493
198
|
const { analyzeProject } = __require("@animus-ui/extract");
|
|
494
|
-
const
|
|
199
|
+
const emitterConfig = JSON.stringify({
|
|
200
|
+
runtime_import: "@animus-ui/system",
|
|
201
|
+
css_module_id: "virtual:animus/styles.css"
|
|
202
|
+
});
|
|
203
|
+
const manifestJson = analyzeProject(JSON.stringify(fileEntries), themeJson, variableMapJson, contextualVarsJson || null, configJson, groupRegistryJson, JSON.stringify(packageMap), !isProd, emitterConfig, selectorAliasesJson, selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesBlocksJson);
|
|
495
204
|
storedManifest = JSON.parse(manifestJson);
|
|
496
205
|
storedManifestJson = manifestJson;
|
|
206
|
+
storedSystemPropMapJson = JSON.stringify(storedManifest?.system_prop_map ?? {});
|
|
207
|
+
const dynamicProps = storedManifest?.dynamic_props ?? {};
|
|
208
|
+
const newDynamicPropsJson = JSON.stringify(dynamicProps);
|
|
209
|
+
if (newDynamicPropsJson !== storedDynamicPropsJson) {
|
|
210
|
+
storedDynamicPropsJson = newDynamicPropsJson;
|
|
211
|
+
const usedTransformNames = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const meta of Object.values(dynamicProps)) if (meta.transform_name) usedTransformNames.add(meta.transform_name);
|
|
213
|
+
if (storedManifest?.components) {
|
|
214
|
+
for (const comp of Object.values(storedManifest.components)) if (comp.replacement) {
|
|
215
|
+
const matches = comp.replacement.matchAll(/transforms\.(\w+)/g);
|
|
216
|
+
for (const match of matches) usedTransformNames.add(match[1]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
storedTransformsSource = "{}";
|
|
220
|
+
}
|
|
497
221
|
bridgeInjected = false;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const tsTmp = Date.now();
|
|
503
|
-
const tmpIn = join(tmpdir(), `animus-css-${tsTmp}.css`);
|
|
504
|
-
const tmpOut = join(tmpdir(), `animus-css-${tsTmp}.out.css`);
|
|
505
|
-
writeFileSync(tmpIn, rawCss);
|
|
506
|
-
execSync(`bun run "${systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
|
|
507
|
-
cwd: rootDir,
|
|
508
|
-
encoding: "utf-8"
|
|
509
|
-
});
|
|
510
|
-
resolvedComponentCss = readFileSync(tmpOut, "utf-8");
|
|
511
|
-
try {
|
|
512
|
-
unlinkSync(tmpIn);
|
|
513
|
-
unlinkSync(tmpOut);
|
|
514
|
-
} catch {}
|
|
515
|
-
} catch (e) {
|
|
516
|
-
console.warn("[animus-extract] System transform resolution failed:", e?.message);
|
|
517
|
-
resolvedComponentCss = resolveTransformPlaceholders(rawCss, rootDir, options.configPath, transformRegistry);
|
|
222
|
+
const newFragments = storedManifest?.component_fragments;
|
|
223
|
+
if (newFragments && typeof newFragments === "object") {
|
|
224
|
+
fragmentCache.clear();
|
|
225
|
+
for (const [id, sheets] of Object.entries(newFragments)) fragmentCache.set(id, sheets);
|
|
518
226
|
}
|
|
519
|
-
|
|
227
|
+
reverseProvenance = storedManifest?.reverse_provenance ?? {};
|
|
228
|
+
storedSheets = storedManifest?.sheets ?? null;
|
|
229
|
+
globalCss = storedManifest?.sheets?.global || "";
|
|
230
|
+
resolvedComponentCss = storedManifest?.css || "";
|
|
520
231
|
resolvedComponentCss = applyUnitFallback(resolvedComponentCss);
|
|
521
232
|
} catch (e) {
|
|
522
|
-
if (options.strict) throw new Error(`[animus-extract] analyzeProject failed: ${e}
|
|
233
|
+
if (options.strict) throw new Error(`[animus-extract] analyzeProject failed: ${e}`, { cause: e });
|
|
523
234
|
console.warn("[animus-extract] analyzeProject failed:", e);
|
|
524
235
|
}
|
|
525
236
|
}
|
|
237
|
+
function runSelfVerify() {
|
|
238
|
+
const failures = [];
|
|
239
|
+
if (resolvedComponentCss.trim().length === 0) failures.push("No component CSS produced — check system file and include patterns");
|
|
240
|
+
if (!variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
|
|
241
|
+
if (`${variableCss}\n${globalCss}\n${resolvedComponentCss}`.includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
|
|
242
|
+
if (storedManifest && resolvedComponentCss.length > 0) {
|
|
243
|
+
const assembled = assembleStylesheet({
|
|
244
|
+
layers: options.layers,
|
|
245
|
+
variableCss,
|
|
246
|
+
globalCss,
|
|
247
|
+
componentCss: resolvedComponentCss
|
|
248
|
+
});
|
|
249
|
+
const baseIdx = assembled.search(/@layer\s+anm-base\s*\{/);
|
|
250
|
+
const variantsIdx = assembled.search(/@layer\s+anm-variants\s*\{/);
|
|
251
|
+
if (baseIdx !== -1 && variantsIdx !== -1 && baseIdx >= variantsIdx) failures.push(`CSS layer ordering violated — @layer anm-base (offset ${baseIdx}) must precede @layer anm-variants (offset ${variantsIdx})`);
|
|
252
|
+
}
|
|
253
|
+
for (const message of failures) {
|
|
254
|
+
const line = `[animus:verify] ${message}`;
|
|
255
|
+
if (options.strict) throw new Error(line);
|
|
256
|
+
if (logger) logger.warn(line, { timestamp: true });
|
|
257
|
+
else console.warn(line);
|
|
258
|
+
}
|
|
259
|
+
if (failures.length === 0) log("[animus:verify] structural self-check passed");
|
|
260
|
+
}
|
|
526
261
|
return {
|
|
527
262
|
name: "animus-extract",
|
|
528
263
|
enforce: "pre",
|
|
264
|
+
configureServer(server) {
|
|
265
|
+
devServer = server;
|
|
266
|
+
},
|
|
529
267
|
configResolved(config) {
|
|
530
268
|
isProd = config.command === "build";
|
|
531
269
|
rootDir = config.root;
|
|
532
270
|
logger = config.logger;
|
|
271
|
+
lcssTargets = resolveLightningTargets(options.targets, rootDir);
|
|
272
|
+
log(`Lightning CSS targets resolved (${Object.keys(lcssTargets).length} browsers)`);
|
|
273
|
+
const rawAlias = config.resolve?.alias;
|
|
274
|
+
if (rawAlias) {
|
|
275
|
+
const entries = [];
|
|
276
|
+
if (Array.isArray(rawAlias)) {
|
|
277
|
+
for (const entry of rawAlias) if (typeof entry.find === "string" && typeof entry.replacement === "string") {
|
|
278
|
+
const replacement = entry.replacement.startsWith(rootDir) ? entry.replacement.slice(rootDir.length + 1) : entry.replacement;
|
|
279
|
+
entries.push({
|
|
280
|
+
pattern: entry.find.endsWith("/") ? entry.find : entry.find + "/",
|
|
281
|
+
replacement: replacement.endsWith("/") ? replacement : replacement + "/",
|
|
282
|
+
type: "prefix"
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
} else if (typeof rawAlias === "object" && rawAlias !== null) {
|
|
286
|
+
for (const [key, value] of Object.entries(rawAlias)) if (typeof value === "string") {
|
|
287
|
+
const replacement = value.startsWith(rootDir) ? value.slice(rootDir.length + 1) : value;
|
|
288
|
+
if (/\.\w+$/.test(replacement)) entries.push({
|
|
289
|
+
pattern: key,
|
|
290
|
+
replacement,
|
|
291
|
+
type: "exact"
|
|
292
|
+
});
|
|
293
|
+
else entries.push({
|
|
294
|
+
pattern: key.endsWith("/") ? key : key + "/",
|
|
295
|
+
replacement: replacement.endsWith("/") ? replacement : replacement + "/",
|
|
296
|
+
type: "prefix"
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
entries.sort((a, b) => b.pattern.length - a.pattern.length);
|
|
301
|
+
if (entries.length > 0) {
|
|
302
|
+
pathAliasesJson = JSON.stringify({ aliases: entries });
|
|
303
|
+
log(`Path aliases forwarded: ${entries.length} entries`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
533
306
|
},
|
|
534
307
|
async buildStart() {
|
|
308
|
+
try {
|
|
309
|
+
const { clearAnalysisCache } = __require("@animus-ui/extract");
|
|
310
|
+
clearAnalysisCache();
|
|
311
|
+
} catch {}
|
|
535
312
|
let t0 = performance.now();
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
313
|
+
loadSystem();
|
|
314
|
+
if (options.layers) {
|
|
315
|
+
validateLayerOrder(options.layers);
|
|
316
|
+
log(`Custom layers: [${options.layers.join(", ")}]`);
|
|
540
317
|
}
|
|
541
318
|
if (verbose) {
|
|
542
319
|
const propCount = Object.keys(JSON.parse(configJson)).length;
|
|
@@ -545,11 +322,30 @@ function animusExtract(options = {}) {
|
|
|
545
322
|
}
|
|
546
323
|
t0 = performance.now();
|
|
547
324
|
const excludePatterns = options.exclude ?? DEFAULT_EXCLUDE;
|
|
548
|
-
|
|
325
|
+
extensionsSet = new Set(options.extensions ?? DEFAULT_EXTENSIONS);
|
|
326
|
+
const shouldHandleMdx = extensionsSet.has(".mdx");
|
|
327
|
+
let mdxMissingDepWarned = false;
|
|
328
|
+
const filePaths = discoverFiles(rootDir, rootDir, excludePatterns, extensionsSet);
|
|
549
329
|
const fileEntries = [];
|
|
550
330
|
for (const filePath of filePaths) try {
|
|
551
|
-
|
|
552
|
-
|
|
331
|
+
let source = readFileSync(filePath, "utf-8");
|
|
332
|
+
let relPath = relative(rootDir, filePath);
|
|
333
|
+
if (shouldHandleMdx && extname(filePath) === ".mdx") {
|
|
334
|
+
const result = await preprocessMdx(source, relPath);
|
|
335
|
+
if (result.kind === "missing-dep") {
|
|
336
|
+
if (!mdxMissingDepWarned) {
|
|
337
|
+
warn("⚠ .mdx in extensions but @mdx-js/mdx not installed; MDX files skipped");
|
|
338
|
+
mdxMissingDepWarned = true;
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (result.kind === "error") {
|
|
343
|
+
warn(`⚠ MDX preprocessing failed for ${relPath}: ${result.error}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
source = result.source;
|
|
347
|
+
relPath = relPath + ".tsx";
|
|
348
|
+
}
|
|
553
349
|
const hash = !isProd ? contentHash(source) : void 0;
|
|
554
350
|
fileEntries.push({
|
|
555
351
|
path: relPath,
|
|
@@ -562,58 +358,78 @@ function animusExtract(options = {}) {
|
|
|
562
358
|
});
|
|
563
359
|
} catch {}
|
|
564
360
|
const localFileCount = fileEntries.length;
|
|
565
|
-
const
|
|
566
|
-
const packageSpecifiers = /* @__PURE__ */ new Set();
|
|
567
|
-
for (const entry of fileEntries) {
|
|
568
|
-
const importRegex = /from\s+['"]([^'"]+)['"]/g;
|
|
569
|
-
let match;
|
|
570
|
-
while ((match = importRegex.exec(entry.source)) !== null) {
|
|
571
|
-
const source = match[1];
|
|
572
|
-
for (const pattern of patterns) {
|
|
573
|
-
const prefix = pattern.replace("*", "");
|
|
574
|
-
if (source.startsWith(prefix) || source === pattern.replace("/*", "")) packageSpecifiers.add(source);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
361
|
+
const packageSpecifiers = extractSystemFilePackages(resolvedSystemPath);
|
|
578
362
|
packageMap = {};
|
|
363
|
+
externalSourceEntries.clear();
|
|
364
|
+
const collectedPkgDirs = [];
|
|
579
365
|
for (const specifier of packageSpecifiers) try {
|
|
580
366
|
const resolved = await this.resolve(specifier);
|
|
581
367
|
if (resolved && resolved.id) {
|
|
582
368
|
const absPath = resolved.id;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
369
|
+
let pkgRoot = dirname(absPath);
|
|
370
|
+
while (pkgRoot !== dirname(pkgRoot) && !existsSync(join(pkgRoot, "package.json"))) pkgRoot = dirname(pkgRoot);
|
|
371
|
+
const srcDir = join(pkgRoot, "src");
|
|
372
|
+
if (existsSync(srcDir)) {
|
|
373
|
+
collectedPkgDirs.push(srcDir);
|
|
374
|
+
const pkgFiles = discoverFiles(srcDir, rootDir, excludePatterns, extensionsSet);
|
|
375
|
+
for (const pkgFile of pkgFiles) {
|
|
376
|
+
let pkgRelPath = relative(rootDir, pkgFile);
|
|
377
|
+
if (!fileEntries.some((e) => e.path === pkgRelPath)) {
|
|
378
|
+
let pkgSource = readFileSync(pkgFile, "utf-8");
|
|
379
|
+
if (shouldHandleMdx && extname(pkgFile) === ".mdx") {
|
|
380
|
+
const result = await preprocessMdx(pkgSource, pkgRelPath);
|
|
381
|
+
if (result.kind === "missing-dep") {
|
|
382
|
+
if (!mdxMissingDepWarned) {
|
|
383
|
+
warn("⚠ .mdx in extensions but @mdx-js/mdx not installed; MDX files skipped");
|
|
384
|
+
mdxMissingDepWarned = true;
|
|
385
|
+
}
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (result.kind === "error") {
|
|
389
|
+
warn(`⚠ MDX preprocessing failed for ${pkgRelPath}: ${result.error}`);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
pkgSource = result.source;
|
|
393
|
+
pkgRelPath = pkgRelPath + ".tsx";
|
|
394
|
+
}
|
|
395
|
+
const pkgHash = !isProd ? contentHash(pkgSource) : void 0;
|
|
396
|
+
fileEntries.push({
|
|
397
|
+
path: pkgRelPath,
|
|
398
|
+
source: pkgSource,
|
|
399
|
+
hash: pkgHash
|
|
400
|
+
});
|
|
401
|
+
if (!isProd && pkgHash) fileCache.set(pkgRelPath, {
|
|
402
|
+
hash: pkgHash,
|
|
403
|
+
source: pkgSource
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const srcEntry = join(srcDir, "index.ts");
|
|
408
|
+
if (existsSync(srcEntry)) {
|
|
409
|
+
packageMap[specifier] = relative(rootDir, srcEntry);
|
|
410
|
+
externalSourceEntries.set(specifier, srcEntry);
|
|
411
|
+
} else packageMap[specifier] = relative(rootDir, absPath);
|
|
412
|
+
} else {
|
|
413
|
+
const relPath = relative(rootDir, absPath);
|
|
414
|
+
if (!fileEntries.some((e) => e.path === relPath)) {
|
|
415
|
+
const entrySource = readFileSync(absPath, "utf-8");
|
|
416
|
+
const entryHash = !isProd ? contentHash(entrySource) : void 0;
|
|
603
417
|
fileEntries.push({
|
|
604
|
-
path:
|
|
605
|
-
source:
|
|
606
|
-
hash:
|
|
418
|
+
path: relPath,
|
|
419
|
+
source: entrySource,
|
|
420
|
+
hash: entryHash
|
|
607
421
|
});
|
|
608
|
-
if (!isProd &&
|
|
609
|
-
hash:
|
|
610
|
-
source:
|
|
422
|
+
if (!isProd && entryHash) fileCache.set(relPath, {
|
|
423
|
+
hash: entryHash,
|
|
424
|
+
source: entrySource
|
|
611
425
|
});
|
|
612
426
|
}
|
|
427
|
+
collectedPkgDirs.push(dirname(absPath));
|
|
428
|
+
packageMap[specifier] = relPath;
|
|
613
429
|
}
|
|
614
|
-
packageMap[specifier] = relPath;
|
|
615
430
|
}
|
|
616
431
|
} catch {}
|
|
432
|
+
externalPackageDirs = collectedPkgDirs;
|
|
617
433
|
const packageFileCount = fileEntries.length - localFileCount;
|
|
618
434
|
log(`Discovered ${fileEntries.length} files (${packageFileCount} from packages) (${Math.round(performance.now() - t0)}ms)`);
|
|
619
435
|
t0 = performance.now();
|
|
@@ -622,9 +438,11 @@ function animusExtract(options = {}) {
|
|
|
622
438
|
const report = storedManifest.report;
|
|
623
439
|
if (report) {
|
|
624
440
|
log(`Extracted ${report.components_extracted}/${report.components_total} components (${Math.round(performance.now() - t0)}ms)`);
|
|
441
|
+
logTimingWaterfall(storedManifest.timing ?? {});
|
|
625
442
|
log(`Reconciliation: ${report.components_extracted} kept, ${report.variants_eliminated} variants pruned, ${report.states_eliminated} states pruned`);
|
|
626
443
|
const details = report.eliminated_details || [];
|
|
627
444
|
for (const d of details) if (d.kind === "component") warn(`⚠ ${d.component} eliminated: ${d.reason}`);
|
|
445
|
+
else if (d.kind === "prospective_component") warn(`⚠ ${d.component} would be eliminated in production: ${d.reason}`);
|
|
628
446
|
else if (d.kind === "variant") warn(`⚠ ${d.component} variant '${d.name}' pruned: ${d.reason}`);
|
|
629
447
|
else if (d.kind === "state") warn(`⚠ ${d.component} state '${d.name}' pruned: ${d.reason}`);
|
|
630
448
|
}
|
|
@@ -633,36 +451,69 @@ function animusExtract(options = {}) {
|
|
|
633
451
|
else if (d.kind === "skip") warn(`⚠ ${d.component}: skipped ${d.message}`);
|
|
634
452
|
log(`CSS: ${resolvedComponentCss.length} bytes (${Object.keys(storedManifest.components || {}).length} components)`);
|
|
635
453
|
if (!isProd && storedSheets) {
|
|
636
|
-
const staticSize = (
|
|
454
|
+
const staticSize = assembleStylesheet({
|
|
455
|
+
layers: options.layers,
|
|
456
|
+
variableCss,
|
|
457
|
+
globalCss
|
|
458
|
+
}).length;
|
|
637
459
|
const componentSize = resolvedComponentCss.length;
|
|
638
460
|
log(`Delivery: split mode — static ${staticSize} bytes, components ${componentSize} bytes (adopted stylesheet)`);
|
|
639
461
|
} else log("Delivery: single file mode (production)");
|
|
640
462
|
}
|
|
463
|
+
const { declaration } = assembleStylesheet({
|
|
464
|
+
layers: options.layers,
|
|
465
|
+
variableCss: "",
|
|
466
|
+
globalCss: "",
|
|
467
|
+
split: true
|
|
468
|
+
});
|
|
469
|
+
layerDeclaration = declaration;
|
|
470
|
+
if (options.verify) runSelfVerify();
|
|
641
471
|
},
|
|
642
472
|
resolveId(id) {
|
|
643
473
|
if (id === VIRTUAL_CSS_ID) return RESOLVED_CSS_ID;
|
|
644
474
|
if (id === VIRTUAL_COMPONENTS_ID) return RESOLVED_COMPONENTS_ID;
|
|
645
475
|
if (id === VIRTUAL_BRIDGE_ID) return RESOLVED_BRIDGE_ID;
|
|
476
|
+
if (id === VIRTUAL_SYSTEM_PROPS_ID) return RESOLVED_SYSTEM_PROPS_ID;
|
|
477
|
+
const srcEntry = externalSourceEntries.get(id);
|
|
478
|
+
if (srcEntry) return srcEntry;
|
|
646
479
|
return null;
|
|
647
480
|
},
|
|
648
481
|
load(id) {
|
|
482
|
+
const lcssOpts = {
|
|
483
|
+
minify: options.minify ?? isProd,
|
|
484
|
+
targets: lcssTargets,
|
|
485
|
+
warnFn: warn
|
|
486
|
+
};
|
|
649
487
|
if (id === RESOLVED_CSS_ID) {
|
|
650
|
-
if (!isProd && storedSheets)
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
488
|
+
if (!isProd && storedSheets) {
|
|
489
|
+
const { variables, body } = assembleStylesheet({
|
|
490
|
+
layers: options.layers,
|
|
491
|
+
variableCss,
|
|
492
|
+
globalCss,
|
|
493
|
+
split: true
|
|
494
|
+
});
|
|
495
|
+
return [variables, postProcessCss(body, {
|
|
496
|
+
...lcssOpts,
|
|
497
|
+
minify: false
|
|
498
|
+
})].filter(Boolean).join("\n");
|
|
499
|
+
}
|
|
500
|
+
const { variables, body } = assembleStylesheet({
|
|
501
|
+
layers: options.layers,
|
|
656
502
|
variableCss,
|
|
657
503
|
globalCss,
|
|
658
|
-
resolvedComponentCss
|
|
659
|
-
|
|
504
|
+
componentCss: resolvedComponentCss,
|
|
505
|
+
split: true
|
|
506
|
+
});
|
|
507
|
+
return [variables, postProcessCss(body, lcssOpts)].filter(Boolean).join("\n");
|
|
660
508
|
}
|
|
661
|
-
if (id === RESOLVED_COMPONENTS_ID) return `export default \`${(resolvedComponentCss || "")
|
|
509
|
+
if (id === RESOLVED_COMPONENTS_ID) return `export default \`${postProcessCss(stripLeadingLayerDeclaration(resolvedComponentCss || ""), {
|
|
510
|
+
...lcssOpts,
|
|
511
|
+
minify: false
|
|
512
|
+
}).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;`;
|
|
662
513
|
if (id === RESOLVED_BRIDGE_ID) return `
|
|
663
514
|
import css from '${VIRTUAL_COMPONENTS_ID}';
|
|
664
515
|
|
|
665
|
-
const GLOBAL_KEY = '
|
|
516
|
+
const GLOBAL_KEY = '__animus_sheet_${createHash("md5").update(options.system).digest("hex").slice(0, 8)}__';
|
|
666
517
|
let sheet = globalThis[GLOBAL_KEY] || null;
|
|
667
518
|
|
|
668
519
|
if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
|
|
@@ -694,14 +545,53 @@ if (import.meta.hot) {
|
|
|
694
545
|
});
|
|
695
546
|
}
|
|
696
547
|
`;
|
|
548
|
+
if (id === RESOLVED_SYSTEM_PROPS_ID) {
|
|
549
|
+
let moduleSource = `export const systemPropMap = ${storedSystemPropMapJson};\nexport const systemPropGroups = ${groupRegistryJson};`;
|
|
550
|
+
if (storedDynamicPropsJson !== "{}") {
|
|
551
|
+
const dynamicProps = JSON.parse(storedDynamicPropsJson);
|
|
552
|
+
const configEntries = {};
|
|
553
|
+
for (const [propName, meta] of Object.entries(dynamicProps)) configEntries[propName] = {
|
|
554
|
+
varName: meta.var_name,
|
|
555
|
+
slotClass: meta.slot_class,
|
|
556
|
+
...meta.transform_name ? { transformName: meta.transform_name } : {},
|
|
557
|
+
...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
|
|
558
|
+
};
|
|
559
|
+
moduleSource += `\nexport const dynamicPropConfig = ${JSON.stringify(configEntries)};`;
|
|
560
|
+
} else moduleSource += `\nexport const dynamicPropConfig = {};`;
|
|
561
|
+
moduleSource += `\nexport const transforms = ${storedTransformsSource};`;
|
|
562
|
+
return moduleSource;
|
|
563
|
+
}
|
|
697
564
|
return null;
|
|
698
565
|
},
|
|
699
566
|
transform(code, id) {
|
|
700
567
|
if (!storedManifest) return null;
|
|
701
|
-
if (
|
|
702
|
-
|
|
568
|
+
if (!externalPackageDirs.some((dir) => id.startsWith(dir))) {
|
|
569
|
+
if (!/\.[jt]sx?$/.test(id)) return null;
|
|
570
|
+
if (id.includes("node_modules")) return null;
|
|
571
|
+
}
|
|
703
572
|
const relativePath = relative(rootDir, id);
|
|
704
|
-
if (!storedManifest.files?.[relativePath]?.length)
|
|
573
|
+
if (!storedManifest.files?.[relativePath]?.length) {
|
|
574
|
+
if (!isProd && !fileCache.has(relativePath)) {
|
|
575
|
+
const hash = contentHash(code);
|
|
576
|
+
fileCache.set(relativePath, {
|
|
577
|
+
hash,
|
|
578
|
+
source: code
|
|
579
|
+
});
|
|
580
|
+
runAnalysis(buildFileEntriesFromCache(fileCache, relativePath));
|
|
581
|
+
const compCount = storedManifest.files?.[relativePath]?.length ?? 0;
|
|
582
|
+
log(`New file detected: ${relativePath} — ${compCount ? `${compCount} components extracted` : "no components"}`);
|
|
583
|
+
if (compCount && devServer) {
|
|
584
|
+
const compModule = devServer.moduleGraph.getModuleById(RESOLVED_COMPONENTS_ID);
|
|
585
|
+
if (compModule) devServer.moduleGraph.invalidateModule(compModule);
|
|
586
|
+
const sysPropModule = devServer.moduleGraph.getModuleById(RESOLVED_SYSTEM_PROPS_ID);
|
|
587
|
+
if (sysPropModule) devServer.moduleGraph.invalidateModule(sysPropModule);
|
|
588
|
+
setTimeout(() => {
|
|
589
|
+
devServer.hot.send({ type: "full-reload" });
|
|
590
|
+
}, 100);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (!storedManifest.files?.[relativePath]?.length) return null;
|
|
594
|
+
}
|
|
705
595
|
try {
|
|
706
596
|
const { transformFile } = __require("@animus-ui/extract");
|
|
707
597
|
const result = transformFile(code, relativePath, storedManifestJson);
|
|
@@ -718,29 +608,35 @@ if (import.meta.hot) {
|
|
|
718
608
|
map: null
|
|
719
609
|
};
|
|
720
610
|
} catch (e) {
|
|
721
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to transform ${id}: ${e}
|
|
611
|
+
if (options.strict) throw new Error(`[animus-extract] Failed to transform ${id}: ${e}`, { cause: e });
|
|
722
612
|
console.warn(`[animus-extract] Failed to transform ${id}:`, e);
|
|
723
613
|
return null;
|
|
724
614
|
}
|
|
725
615
|
},
|
|
726
|
-
|
|
616
|
+
transformIndexHtml: {
|
|
617
|
+
order: "pre",
|
|
618
|
+
handler() {
|
|
619
|
+
if (!layerDeclaration) return [];
|
|
620
|
+
return [{
|
|
621
|
+
tag: "style",
|
|
622
|
+
attrs: { "data-animus-layers": "" },
|
|
623
|
+
children: layerDeclaration,
|
|
624
|
+
injectTo: "head-prepend"
|
|
625
|
+
}];
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
async handleHotUpdate({ file, server: hmrServer, modules }) {
|
|
727
629
|
if (isProd) return;
|
|
728
630
|
const ext = extname(file);
|
|
729
|
-
if (!
|
|
730
|
-
|
|
631
|
+
if (!extensionsSet.has(ext)) return;
|
|
632
|
+
const excludePatterns = options.exclude ?? DEFAULT_EXCLUDE;
|
|
633
|
+
if (!externalPackageDirs.some((dir) => file.startsWith(dir)) && excludePatterns.some((pattern) => file.includes(pattern) || relative(rootDir, file).includes(pattern))) return;
|
|
731
634
|
const absFile = resolve(file);
|
|
732
635
|
const relPath = relative(rootDir, absFile);
|
|
733
|
-
|
|
734
|
-
const isThemeChange = resolvedThemePath && absFile === resolve(resolvedThemePath);
|
|
735
|
-
const isSystemChange = resolvedSystemPath && absFile === resolve(resolvedSystemPath);
|
|
736
|
-
if (isConfigChange || isThemeChange || isSystemChange) {
|
|
636
|
+
if (resolvedSystemPath && absFile === resolve(resolvedSystemPath)) {
|
|
737
637
|
const resetStart = performance.now();
|
|
738
638
|
log(`HMR geological reset: ${relPath}`);
|
|
739
|
-
|
|
740
|
-
else {
|
|
741
|
-
if (isConfigChange) loadConfig();
|
|
742
|
-
if (isThemeChange) loadTheme();
|
|
743
|
-
}
|
|
639
|
+
loadSystem();
|
|
744
640
|
try {
|
|
745
641
|
const { clearAnalysisCache } = __require("@animus-ui/extract");
|
|
746
642
|
clearAnalysisCache();
|
|
@@ -764,6 +660,11 @@ if (import.meta.hot) {
|
|
|
764
660
|
hmrServer.moduleGraph.invalidateModule(compModule);
|
|
765
661
|
geologicalModules.push(compModule);
|
|
766
662
|
}
|
|
663
|
+
const sysPropModule = hmrServer.moduleGraph.getModuleById(RESOLVED_SYSTEM_PROPS_ID);
|
|
664
|
+
if (sysPropModule) {
|
|
665
|
+
hmrServer.moduleGraph.invalidateModule(sysPropModule);
|
|
666
|
+
geologicalModules.push(sysPropModule);
|
|
667
|
+
}
|
|
767
668
|
return geologicalModules;
|
|
768
669
|
}
|
|
769
670
|
let source;
|
|
@@ -772,19 +673,47 @@ if (import.meta.hot) {
|
|
|
772
673
|
} catch {
|
|
773
674
|
return;
|
|
774
675
|
}
|
|
676
|
+
let scannerRelPath = relPath;
|
|
677
|
+
if (ext === ".mdx") {
|
|
678
|
+
const result = await preprocessMdx(source, relPath);
|
|
679
|
+
if (result.kind === "missing-dep") {
|
|
680
|
+
warn("⚠ .mdx HMR skipped: @mdx-js/mdx not installed; restart dev server after installing");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (result.kind === "error") {
|
|
684
|
+
warn(`⚠ MDX preprocessing failed for ${relPath}: ${result.error}`);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
source = result.source;
|
|
688
|
+
scannerRelPath = relPath + ".tsx";
|
|
689
|
+
}
|
|
775
690
|
const hash = contentHash(source);
|
|
776
|
-
const cached = fileCache.get(
|
|
691
|
+
const cached = fileCache.get(scannerRelPath);
|
|
777
692
|
if (cached && cached.hash === hash) {
|
|
778
|
-
log(`HMR skip: ${
|
|
693
|
+
log(`HMR skip: ${scannerRelPath} (unchanged)`);
|
|
779
694
|
return [];
|
|
780
695
|
}
|
|
781
|
-
fileCache.set(
|
|
696
|
+
fileCache.set(scannerRelPath, {
|
|
782
697
|
hash,
|
|
783
698
|
source
|
|
784
699
|
});
|
|
785
700
|
const hmrStart = performance.now();
|
|
786
701
|
const prevReplacements = /* @__PURE__ */ new Map();
|
|
787
702
|
if (storedManifest?.components) for (const [id, desc] of Object.entries(storedManifest.components)) prevReplacements.set(id, desc.replacement ?? "");
|
|
703
|
+
const directComponentIds = storedManifest?.files?.[scannerRelPath] ?? [];
|
|
704
|
+
const invalidatedIds = new Set(directComponentIds);
|
|
705
|
+
const queue = [...directComponentIds];
|
|
706
|
+
while (queue.length > 0) {
|
|
707
|
+
const parentId = queue.shift();
|
|
708
|
+
const children = reverseProvenance[parentId];
|
|
709
|
+
if (children) {
|
|
710
|
+
for (const childId of children) if (!invalidatedIds.has(childId)) {
|
|
711
|
+
invalidatedIds.add(childId);
|
|
712
|
+
queue.push(childId);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (verbose && invalidatedIds.size > directComponentIds.length) log(`HMR: ${directComponentIds.length} direct + ${invalidatedIds.size - directComponentIds.length} transitive components invalidated`);
|
|
788
717
|
const analysisStart = performance.now();
|
|
789
718
|
runAnalysis(buildFileEntriesFromCache(fileCache, relPath));
|
|
790
719
|
const analysisMs = Math.round(performance.now() - analysisStart);
|
|
@@ -794,6 +723,11 @@ if (import.meta.hot) {
|
|
|
794
723
|
hmrServer.moduleGraph.invalidateModule(compModule);
|
|
795
724
|
modulesToUpdate.push(compModule);
|
|
796
725
|
}
|
|
726
|
+
const sysPropModule = hmrServer.moduleGraph.getModuleById(RESOLVED_SYSTEM_PROPS_ID);
|
|
727
|
+
if (sysPropModule) {
|
|
728
|
+
hmrServer.moduleGraph.invalidateModule(sysPropModule);
|
|
729
|
+
modulesToUpdate.push(sysPropModule);
|
|
730
|
+
}
|
|
797
731
|
if (storedManifest?.components) {
|
|
798
732
|
const staleFiles = /* @__PURE__ */ new Set();
|
|
799
733
|
for (const [id, desc] of Object.entries(storedManifest.components)) if ((desc.replacement ?? "") !== (prevReplacements.get(id) ?? "")) staleFiles.add(desc.file);
|
|
@@ -810,9 +744,10 @@ if (import.meta.hot) {
|
|
|
810
744
|
}
|
|
811
745
|
const hmrMs = Math.round(performance.now() - hmrStart);
|
|
812
746
|
log(`HMR update: ${relPath} — analysis ${analysisMs}ms, ${modulesToUpdate.length - modules.length} modules invalidated, total ${hmrMs}ms`);
|
|
747
|
+
logTimingWaterfall(storedManifest?.timing ?? {});
|
|
813
748
|
if (modulesToUpdate.length > modules.length) return modulesToUpdate;
|
|
814
749
|
}
|
|
815
750
|
};
|
|
816
751
|
}
|
|
817
752
|
//#endregion
|
|
818
|
-
export { animusExtract, animusExtract as default,
|
|
753
|
+
export { animusExtract, animusExtract as default, discoverFiles };
|