@animus-ui/vite-plugin 0.1.0-next.9 → 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 +383 -502
- 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,113 +1,14 @@
|
|
|
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") for (const [modeName, modeTokens] of Object.entries(theme._tokens.modes)) {
|
|
69
|
-
if (modeTokens == null || typeof modeTokens !== "object") continue;
|
|
70
|
-
const modeLines = [];
|
|
71
|
-
flattenModeTokens(modeLines, modeTokens, "");
|
|
72
|
-
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
73
|
-
}
|
|
74
|
-
return parts.join("\n\n");
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Recursively flatten mode token objects into CSS variable declarations.
|
|
78
|
-
*
|
|
79
|
-
* Examples:
|
|
80
|
-
* { primary: '#ff80bf' } → ' --color-primary: #ff80bf;'
|
|
81
|
-
* { background: { _: '#fff', muted: '#f5f5f5' } }
|
|
82
|
-
* → ' --color-background: #fff;'
|
|
83
|
-
* ' --color-background-muted: #f5f5f5;'
|
|
84
|
-
*
|
|
85
|
-
* The special key `_` maps to the base name (no suffix).
|
|
86
|
-
*/
|
|
87
|
-
function flattenModeTokens(lines, obj, prefix) {
|
|
88
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
89
|
-
let namePart;
|
|
90
|
-
if (key === "_") namePart = prefix;
|
|
91
|
-
else namePart = prefix ? `${prefix}-${key}` : key;
|
|
92
|
-
if (typeof value === "string" || typeof value === "number") lines.push(` --color-${namePart}: ${value};`);
|
|
93
|
-
else if (value != null && typeof value === "object" && !Array.isArray(value)) flattenModeTokens(lines, value, namePart);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function flattenScale(flat, prefix, obj, parentKey = "") {
|
|
97
|
-
if (typeof obj !== "object" || obj === null) {
|
|
98
|
-
const key = parentKey ? `${prefix}.${parentKey}` : prefix;
|
|
99
|
-
flat[key] = String(obj);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
103
|
-
const fullKey = parentKey ? `${parentKey}-${key}` : key;
|
|
104
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenScale(flat, prefix, value, fullKey);
|
|
105
|
-
else flat[`${prefix}.${fullKey}`] = String(value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
11
|
//#region src/index.ts
|
|
110
|
-
const __pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
111
12
|
const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
|
|
112
13
|
const RESOLVED_CSS_ID = "\0virtual:animus/styles.css";
|
|
113
14
|
const VIRTUAL_COMPONENTS_ID = "virtual:animus/components.js";
|
|
@@ -116,19 +17,13 @@ const VIRTUAL_BRIDGE_ID = "virtual:animus/hmr-bridge.js";
|
|
|
116
17
|
const RESOLVED_BRIDGE_ID = "\0virtual:animus/hmr-bridge.js";
|
|
117
18
|
const VIRTUAL_SYSTEM_PROPS_ID = "virtual:animus/system-props";
|
|
118
19
|
const RESOLVED_SYSTEM_PROPS_ID = "\0virtual:animus/system-props";
|
|
119
|
-
const DEFAULT_EXTENSIONS = new Set([
|
|
120
|
-
".ts",
|
|
121
|
-
".tsx",
|
|
122
|
-
".js",
|
|
123
|
-
".jsx"
|
|
124
|
-
]);
|
|
125
20
|
const DEFAULT_EXCLUDE = [
|
|
126
21
|
"node_modules",
|
|
127
22
|
"dist",
|
|
128
23
|
".test.",
|
|
129
24
|
".spec."
|
|
130
25
|
];
|
|
131
|
-
function discoverFiles(dir, rootDir, excludePatterns) {
|
|
26
|
+
function discoverFiles(dir, rootDir, excludePatterns, extensionsSet) {
|
|
132
27
|
const results = [];
|
|
133
28
|
let entries;
|
|
134
29
|
try {
|
|
@@ -146,115 +41,42 @@ function discoverFiles(dir, rootDir, excludePatterns) {
|
|
|
146
41
|
} catch {
|
|
147
42
|
continue;
|
|
148
43
|
}
|
|
149
|
-
if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns));
|
|
150
|
-
else if (
|
|
44
|
+
if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns, extensionsSet));
|
|
45
|
+
else if (extensionsSet.has(extname(entry))) results.push(fullPath);
|
|
151
46
|
}
|
|
152
47
|
return results;
|
|
153
48
|
}
|
|
154
49
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* Rust emits `__TRANSFORM__name__rawValue__` for props with transforms.
|
|
158
|
-
* This function resolves each placeholder using the actual JS transform
|
|
159
|
-
* functions from the config.
|
|
50
|
+
* Resolve browser targets for Lightning CSS.
|
|
51
|
+
* Priority: explicit config → project browserslist → 'defaults' fallback.
|
|
160
52
|
*/
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return typeof result === "object" ? JSON.stringify(result) : String(result);
|
|
170
|
-
});
|
|
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);
|
|
171
61
|
}
|
|
172
62
|
/**
|
|
173
|
-
* CSS
|
|
174
|
-
*
|
|
175
|
-
* 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.
|
|
176
65
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"flex-negative",
|
|
192
|
-
"flex-order",
|
|
193
|
-
"grid-area",
|
|
194
|
-
"grid-row",
|
|
195
|
-
"grid-row-end",
|
|
196
|
-
"grid-row-span",
|
|
197
|
-
"grid-row-start",
|
|
198
|
-
"grid-column",
|
|
199
|
-
"grid-column-end",
|
|
200
|
-
"grid-column-span",
|
|
201
|
-
"grid-column-start",
|
|
202
|
-
"font-weight",
|
|
203
|
-
"line-clamp",
|
|
204
|
-
"line-height",
|
|
205
|
-
"opacity",
|
|
206
|
-
"order",
|
|
207
|
-
"orphans",
|
|
208
|
-
"tab-size",
|
|
209
|
-
"widows",
|
|
210
|
-
"z-index",
|
|
211
|
-
"zoom",
|
|
212
|
-
"fill-opacity",
|
|
213
|
-
"flood-opacity",
|
|
214
|
-
"stop-opacity",
|
|
215
|
-
"stroke-dasharray",
|
|
216
|
-
"stroke-dashoffset",
|
|
217
|
-
"stroke-miterlimit",
|
|
218
|
-
"stroke-opacity",
|
|
219
|
-
"stroke-width"
|
|
220
|
-
]);
|
|
221
|
-
/**
|
|
222
|
-
* Append `px` to bare numeric values in CSS declarations for properties
|
|
223
|
-
* that expect length units. Unitless properties are preserved as-is.
|
|
224
|
-
* Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
|
|
225
|
-
*/
|
|
226
|
-
function applyUnitFallback(css) {
|
|
227
|
-
return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
|
|
228
|
-
if (UNITLESS_PROPERTIES.has(prop)) return match;
|
|
229
|
-
let depth = 0;
|
|
230
|
-
let fixed = "";
|
|
231
|
-
let i = 0;
|
|
232
|
-
while (i < value.length) if (value[i] === "(") {
|
|
233
|
-
depth++;
|
|
234
|
-
fixed += value[i];
|
|
235
|
-
i++;
|
|
236
|
-
} else if (value[i] === ")") {
|
|
237
|
-
depth--;
|
|
238
|
-
fixed += value[i];
|
|
239
|
-
i++;
|
|
240
|
-
} else if (depth > 0) {
|
|
241
|
-
fixed += value[i];
|
|
242
|
-
i++;
|
|
243
|
-
} else {
|
|
244
|
-
const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
|
|
245
|
-
if (numMatch) {
|
|
246
|
-
const num = numMatch[1];
|
|
247
|
-
const after = value[i + num.length];
|
|
248
|
-
if (after && /[a-z%]/i.test(after)) fixed += num;
|
|
249
|
-
else fixed += num + "px";
|
|
250
|
-
i += num.length;
|
|
251
|
-
} else {
|
|
252
|
-
fixed += value[i];
|
|
253
|
-
i++;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return fixed !== value ? `${prop}:${fixed};` : match;
|
|
257
|
-
});
|
|
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
|
+
}
|
|
258
80
|
}
|
|
259
81
|
/** Compute MD5 content hash for a string. */
|
|
260
82
|
function contentHash(source) {
|
|
@@ -275,44 +97,14 @@ function buildFileEntriesFromCache(cache, changedPath) {
|
|
|
275
97
|
});
|
|
276
98
|
return entries;
|
|
277
99
|
}
|
|
278
|
-
|
|
279
|
-
* Resolve transform placeholders in CSS via bun subprocess.
|
|
280
|
-
* Falls back to in-process resolution if the subprocess fails.
|
|
281
|
-
*/
|
|
282
|
-
function resolveTransformPlaceholders(css, rootDir, configPath, transformRegistry) {
|
|
283
|
-
if (!css.includes("__TRANSFORM__")) return css;
|
|
284
|
-
try {
|
|
285
|
-
const ts = Date.now();
|
|
286
|
-
const tmpCss = join(tmpdir(), `animus-transforms-${ts}.css`);
|
|
287
|
-
const tmpOut = join(tmpdir(), `animus-transforms-${ts}.out.css`);
|
|
288
|
-
writeFileSync(tmpCss, css);
|
|
289
|
-
const candidates = [join(__pluginDir, "resolve-transforms.ts"), join(__pluginDir, "..", "src", "resolve-transforms.ts")];
|
|
290
|
-
try {
|
|
291
|
-
const pkgDir = dirname(__require.resolve("@animus-ui/vite-plugin/package.json", { paths: [rootDir] }));
|
|
292
|
-
candidates.push(join(pkgDir, "src", "resolve-transforms.ts"));
|
|
293
|
-
} catch {}
|
|
294
|
-
const scriptPath = candidates.find((p) => existsSync(p));
|
|
295
|
-
if (!scriptPath) throw new Error(`resolve-transforms.ts not found in: ${candidates.join(", ")}`);
|
|
296
|
-
execSync(`bun run "${scriptPath}" "${tmpCss}" "${tmpOut}" ${configPath ? `"${resolve(rootDir, configPath)}"` : ""}`, {
|
|
297
|
-
cwd: rootDir,
|
|
298
|
-
encoding: "utf-8"
|
|
299
|
-
});
|
|
300
|
-
const resolved = readFileSync(tmpOut, "utf-8");
|
|
301
|
-
try {
|
|
302
|
-
unlinkSync(tmpCss);
|
|
303
|
-
unlinkSync(tmpOut);
|
|
304
|
-
} catch {}
|
|
305
|
-
return resolved;
|
|
306
|
-
} catch (e) {
|
|
307
|
-
console.error("[animus-extract] Transform resolution failed:", e?.message || e);
|
|
308
|
-
return applyTransformPlaceholders(css, transformRegistry);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
function animusExtract(options = {}) {
|
|
100
|
+
function animusExtract(options) {
|
|
312
101
|
let themeJson = "{}";
|
|
313
102
|
let variableMapJson = "{}";
|
|
103
|
+
let contextualVarsJson = "{}";
|
|
314
104
|
let configJson = "{}";
|
|
315
105
|
let groupRegistryJson = "{}";
|
|
106
|
+
let selectorAliasesJson = null;
|
|
107
|
+
let selectorOrderJson = null;
|
|
316
108
|
let isProd = false;
|
|
317
109
|
let rootDir = "";
|
|
318
110
|
const verbose = options.verbose || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
|
|
@@ -323,176 +115,92 @@ function animusExtract(options = {}) {
|
|
|
323
115
|
function warn(msg) {
|
|
324
116
|
(logger ?? console).warn(`[animus] ${msg}`);
|
|
325
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 = {};
|
|
326
140
|
let variableCss = "";
|
|
327
141
|
let globalCss = "";
|
|
328
|
-
let
|
|
142
|
+
let globalStyleBlocksJson = null;
|
|
143
|
+
let keyframesBlocksJson = null;
|
|
144
|
+
let pathAliasesJson = null;
|
|
145
|
+
let extensionsSet = new Set(options.extensions ?? DEFAULT_EXTENSIONS);
|
|
329
146
|
let storedManifest = null;
|
|
330
147
|
let storedManifestJson = "";
|
|
331
148
|
let resolvedComponentCss = "";
|
|
332
149
|
let storedSheets = null;
|
|
150
|
+
let layerDeclaration = "";
|
|
151
|
+
let fragmentCache = /* @__PURE__ */ new Map();
|
|
152
|
+
let reverseProvenance = {};
|
|
333
153
|
let storedSystemPropMapJson = "{}";
|
|
334
154
|
let storedDynamicPropsJson = "{}";
|
|
335
|
-
let storedTransformsSource = "";
|
|
155
|
+
let storedTransformsSource = "{}";
|
|
336
156
|
const fileCache = /* @__PURE__ */ new Map();
|
|
337
157
|
let packageMap = {};
|
|
158
|
+
let externalPackageDirs = [];
|
|
159
|
+
const externalSourceEntries = /* @__PURE__ */ new Map();
|
|
160
|
+
let devServer;
|
|
338
161
|
let bridgeInjected = false;
|
|
339
|
-
let resolvedConfigPath = null;
|
|
340
|
-
let resolvedThemePath = null;
|
|
341
162
|
let resolvedSystemPath = null;
|
|
342
|
-
/** Resolve config via bun subprocess. Updates configJson + groupRegistryJson. */
|
|
343
|
-
function loadConfig() {
|
|
344
|
-
if (options.config) {
|
|
345
|
-
configJson = options.config;
|
|
346
|
-
groupRegistryJson = options.groupRegistry ?? groupRegistryJson;
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const configSource = options.configPath ? resolve(rootDir, options.configPath) : "@animus-ui/core";
|
|
350
|
-
const requireExpr = options.configPath ? `require('${configSource.replace(/\\/g, "/")}')` : `require('@animus-ui/core')`;
|
|
351
|
-
if (options.configPath) resolvedConfigPath = resolve(rootDir, options.configPath);
|
|
352
|
-
try {
|
|
353
|
-
const result = execSync(`bun -e "const m = ${requireExpr}; const r = m.getExtractConfig(); process.stdout.write(JSON.stringify(r))"`, {
|
|
354
|
-
cwd: rootDir,
|
|
355
|
-
encoding: "utf-8"
|
|
356
|
-
});
|
|
357
|
-
const { propConfig, groupRegistry } = JSON.parse(result);
|
|
358
|
-
configJson = propConfig;
|
|
359
|
-
groupRegistryJson = groupRegistry;
|
|
360
|
-
} catch (e) {
|
|
361
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to auto-import config from ${configSource}: ${e}`);
|
|
362
|
-
console.warn(`[animus-extract] Failed to auto-import config from ${configSource}:`, e);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/** Resolve theme via bun subprocess. Updates themeJson + variableCss. */
|
|
366
|
-
function loadTheme() {
|
|
367
|
-
if (options.theme) {
|
|
368
|
-
if (typeof options.theme === "string") {
|
|
369
|
-
themeJson = options.theme;
|
|
370
|
-
variableCss = "";
|
|
371
|
-
} else {
|
|
372
|
-
themeJson = options.theme.scales;
|
|
373
|
-
variableCss = options.theme.variables;
|
|
374
|
-
}
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
resolvedThemePath = null;
|
|
378
|
-
if (options.themePath) {
|
|
379
|
-
const p = resolve(rootDir, options.themePath);
|
|
380
|
-
if (existsSync(p)) resolvedThemePath = p;
|
|
381
|
-
else if (options.strict) throw new Error(`[animus-extract] Theme file not found: ${p}`);
|
|
382
|
-
else console.warn(`[animus-extract] Theme file not found: ${p}`);
|
|
383
|
-
} else {
|
|
384
|
-
const candidates = [
|
|
385
|
-
join(rootDir, "src", "theme.ts"),
|
|
386
|
-
join(rootDir, "src", "theme.js"),
|
|
387
|
-
join(rootDir, "theme.ts"),
|
|
388
|
-
join(rootDir, "theme.js")
|
|
389
|
-
];
|
|
390
|
-
for (const candidate of candidates) if (existsSync(candidate)) {
|
|
391
|
-
resolvedThemePath = candidate;
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if (resolvedThemePath) try {
|
|
396
|
-
const themeJson_ = execSync(`bun -e "${[
|
|
397
|
-
`const m = require(${JSON.stringify(resolvedThemePath)});`,
|
|
398
|
-
`const theme = m.theme || m.default;`,
|
|
399
|
-
`process.stdout.write(JSON.stringify(theme || {}));`
|
|
400
|
-
].join(" ").replace(/"/g, "\\\"")}"`, {
|
|
401
|
-
cwd: rootDir,
|
|
402
|
-
encoding: "utf-8"
|
|
403
|
-
});
|
|
404
|
-
const theme = JSON.parse(themeJson_);
|
|
405
|
-
if (theme && Object.keys(theme).length > 0) {
|
|
406
|
-
const result = evaluateThemeObject(theme);
|
|
407
|
-
themeJson = result.scalesJson;
|
|
408
|
-
variableMapJson = result.variableMapJson;
|
|
409
|
-
variableCss = result.variableCss;
|
|
410
|
-
}
|
|
411
|
-
} catch (e) {
|
|
412
|
-
if (options.strict) throw new Error(`[animus-extract] Failed to load theme from ${resolvedThemePath}: ${e}`);
|
|
413
|
-
console.warn(`[animus-extract] Failed to load theme from ${resolvedThemePath}:`, e);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
163
|
/**
|
|
417
|
-
* Load a SystemInstance via
|
|
418
|
-
*
|
|
419
|
-
*
|
|
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.
|
|
420
168
|
*/
|
|
421
169
|
function loadSystem() {
|
|
422
|
-
if (!options.system) return;
|
|
423
170
|
resolvedSystemPath = resolve(rootDir, options.system);
|
|
424
171
|
try {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
} catch {}
|
|
438
|
-
const parsed = JSON.parse(result);
|
|
439
|
-
configJson = parsed.propConfig;
|
|
440
|
-
groupRegistryJson = parsed.groupRegistry;
|
|
441
|
-
if (parsed.tokens && Object.keys(parsed.tokens).length > 0) {
|
|
442
|
-
const result = evaluateThemeObject(parsed.tokens);
|
|
443
|
-
themeJson = result.scalesJson;
|
|
444
|
-
variableMapJson = result.variableMapJson;
|
|
445
|
-
variableCss = result.variableCss;
|
|
446
|
-
} else if (logger) logger.warn("[animus] No tokens export found in system module — CSS variables will not be generated. Export your theme as `tokens` or `theme`.");
|
|
447
|
-
if (parsed.globalStyles) {
|
|
448
|
-
const hasReset = parsed.globalStyles.reset && Object.keys(parsed.globalStyles.reset).length > 0;
|
|
449
|
-
const hasGlobal = parsed.globalStyles.global && Object.keys(parsed.globalStyles.global).length > 0;
|
|
450
|
-
if (hasReset || hasGlobal) try {
|
|
451
|
-
const gsTmp = Date.now();
|
|
452
|
-
const gsThemeFile = join(tmpdir(), `animus-gs-theme-${gsTmp}.json`);
|
|
453
|
-
const gsOut = join(tmpdir(), `animus-global-${gsTmp}.json`);
|
|
454
|
-
writeFileSync(gsThemeFile, themeJson);
|
|
455
|
-
const gsCandidates = [join(__pluginDir, "resolve-global-styles.ts"), join(__pluginDir, "..", "src", "resolve-global-styles.ts")];
|
|
456
|
-
try {
|
|
457
|
-
const pkgDir = dirname(__require.resolve("@animus-ui/vite-plugin/package.json", { paths: [rootDir] }));
|
|
458
|
-
gsCandidates.push(join(pkgDir, "src", "resolve-global-styles.ts"));
|
|
459
|
-
} catch {}
|
|
460
|
-
const gsScriptPath = gsCandidates.find((p) => existsSync(p));
|
|
461
|
-
if (!gsScriptPath) throw new Error(`resolve-global-styles.ts not found in: ${gsCandidates.join(", ")}`);
|
|
462
|
-
execSync(`bun run "${gsScriptPath}" "${resolvedSystemPath}" "${gsThemeFile}" "${gsOut}"`, {
|
|
463
|
-
cwd: rootDir,
|
|
464
|
-
encoding: "utf-8"
|
|
465
|
-
});
|
|
466
|
-
const gsResult = JSON.parse(readFileSync(gsOut, "utf-8"));
|
|
467
|
-
const parts = [gsResult.reset, gsResult.global].filter(Boolean);
|
|
468
|
-
if (parts.length > 0) globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
|
|
469
|
-
try {
|
|
470
|
-
unlinkSync(gsThemeFile);
|
|
471
|
-
unlinkSync(gsOut);
|
|
472
|
-
} catch {}
|
|
473
|
-
} catch (e) {
|
|
474
|
-
console.warn("[animus-extract] Global styles resolution failed:", e?.message || e);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
if (parsed.transformNames && parsed.transformNames.length > 0) {
|
|
478
|
-
const tsTmp = Date.now();
|
|
479
|
-
const tmpResolve = join(tmpdir(), `animus-transforms-resolve-${tsTmp}.js`);
|
|
480
|
-
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`);
|
|
481
|
-
globalThis.__animus_system_resolve_script = tmpResolve;
|
|
482
|
-
}
|
|
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;
|
|
483
184
|
} catch (e) {
|
|
484
|
-
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 });
|
|
485
186
|
console.warn(`[animus-extract] Failed to load system from ${resolvedSystemPath}:`, e);
|
|
486
187
|
}
|
|
487
188
|
}
|
|
488
189
|
/**
|
|
489
190
|
* Run project analysis and update the manifest.
|
|
490
|
-
*
|
|
191
|
+
*
|
|
192
|
+
* Pipeline: NAPI analyzeProject → unit fallback.
|
|
193
|
+
* Transforms are resolved in-process via boa_engine during Rust extraction.
|
|
194
|
+
* No subprocess needed.
|
|
491
195
|
*/
|
|
492
196
|
function runAnalysis(fileEntries) {
|
|
493
197
|
try {
|
|
494
198
|
const { analyzeProject } = __require("@animus-ui/extract");
|
|
495
|
-
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);
|
|
496
204
|
storedManifest = JSON.parse(manifestJson);
|
|
497
205
|
storedManifestJson = manifestJson;
|
|
498
206
|
storedSystemPropMapJson = JSON.stringify(storedManifest?.system_prop_map ?? {});
|
|
@@ -508,62 +216,104 @@ function animusExtract(options = {}) {
|
|
|
508
216
|
for (const match of matches) usedTransformNames.add(match[1]);
|
|
509
217
|
}
|
|
510
218
|
}
|
|
511
|
-
|
|
512
|
-
const transformParts = [];
|
|
513
|
-
for (const name of usedTransformNames) {
|
|
514
|
-
const fn = transformRegistry.get(name);
|
|
515
|
-
if (fn) try {
|
|
516
|
-
transformParts.push(`${JSON.stringify(name)}: ${fn.toString()}`);
|
|
517
|
-
} catch {
|
|
518
|
-
console.warn(`[animus-extract] Could not serialize transform "${name}" — dynamic props using it will fall back to raw values`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
storedTransformsSource = transformParts.length > 0 ? `{ ${transformParts.join(", ")} }` : "{}";
|
|
522
|
-
} else storedTransformsSource = "{}";
|
|
219
|
+
storedTransformsSource = "{}";
|
|
523
220
|
}
|
|
524
221
|
bridgeInjected = false;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const tsTmp = Date.now();
|
|
530
|
-
const tmpIn = join(tmpdir(), `animus-css-${tsTmp}.css`);
|
|
531
|
-
const tmpOut = join(tmpdir(), `animus-css-${tsTmp}.out.css`);
|
|
532
|
-
writeFileSync(tmpIn, rawCss);
|
|
533
|
-
execSync(`bun run "${systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
|
|
534
|
-
cwd: rootDir,
|
|
535
|
-
encoding: "utf-8"
|
|
536
|
-
});
|
|
537
|
-
resolvedComponentCss = readFileSync(tmpOut, "utf-8");
|
|
538
|
-
try {
|
|
539
|
-
unlinkSync(tmpIn);
|
|
540
|
-
unlinkSync(tmpOut);
|
|
541
|
-
} catch {}
|
|
542
|
-
} catch (e) {
|
|
543
|
-
console.warn("[animus-extract] System transform resolution failed:", e?.message);
|
|
544
|
-
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);
|
|
545
226
|
}
|
|
546
|
-
|
|
227
|
+
reverseProvenance = storedManifest?.reverse_provenance ?? {};
|
|
228
|
+
storedSheets = storedManifest?.sheets ?? null;
|
|
229
|
+
globalCss = storedManifest?.sheets?.global || "";
|
|
230
|
+
resolvedComponentCss = storedManifest?.css || "";
|
|
547
231
|
resolvedComponentCss = applyUnitFallback(resolvedComponentCss);
|
|
548
232
|
} catch (e) {
|
|
549
|
-
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 });
|
|
550
234
|
console.warn("[animus-extract] analyzeProject failed:", e);
|
|
551
235
|
}
|
|
552
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
|
+
}
|
|
553
261
|
return {
|
|
554
262
|
name: "animus-extract",
|
|
555
263
|
enforce: "pre",
|
|
264
|
+
configureServer(server) {
|
|
265
|
+
devServer = server;
|
|
266
|
+
},
|
|
556
267
|
configResolved(config) {
|
|
557
268
|
isProd = config.command === "build";
|
|
558
269
|
rootDir = config.root;
|
|
559
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
|
+
}
|
|
560
306
|
},
|
|
561
307
|
async buildStart() {
|
|
308
|
+
try {
|
|
309
|
+
const { clearAnalysisCache } = __require("@animus-ui/extract");
|
|
310
|
+
clearAnalysisCache();
|
|
311
|
+
} catch {}
|
|
562
312
|
let t0 = performance.now();
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
313
|
+
loadSystem();
|
|
314
|
+
if (options.layers) {
|
|
315
|
+
validateLayerOrder(options.layers);
|
|
316
|
+
log(`Custom layers: [${options.layers.join(", ")}]`);
|
|
567
317
|
}
|
|
568
318
|
if (verbose) {
|
|
569
319
|
const propCount = Object.keys(JSON.parse(configJson)).length;
|
|
@@ -572,11 +322,30 @@ function animusExtract(options = {}) {
|
|
|
572
322
|
}
|
|
573
323
|
t0 = performance.now();
|
|
574
324
|
const excludePatterns = options.exclude ?? DEFAULT_EXCLUDE;
|
|
575
|
-
|
|
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);
|
|
576
329
|
const fileEntries = [];
|
|
577
330
|
for (const filePath of filePaths) try {
|
|
578
|
-
|
|
579
|
-
|
|
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
|
+
}
|
|
580
349
|
const hash = !isProd ? contentHash(source) : void 0;
|
|
581
350
|
fileEntries.push({
|
|
582
351
|
path: relPath,
|
|
@@ -589,58 +358,78 @@ function animusExtract(options = {}) {
|
|
|
589
358
|
});
|
|
590
359
|
} catch {}
|
|
591
360
|
const localFileCount = fileEntries.length;
|
|
592
|
-
const
|
|
593
|
-
const packageSpecifiers = /* @__PURE__ */ new Set();
|
|
594
|
-
for (const entry of fileEntries) {
|
|
595
|
-
const importRegex = /from\s+['"]([^'"]+)['"]/g;
|
|
596
|
-
let match;
|
|
597
|
-
while ((match = importRegex.exec(entry.source)) !== null) {
|
|
598
|
-
const source = match[1];
|
|
599
|
-
for (const pattern of patterns) {
|
|
600
|
-
const prefix = pattern.replace("*", "");
|
|
601
|
-
if (source.startsWith(prefix) || source === pattern.replace("/*", "")) packageSpecifiers.add(source);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
361
|
+
const packageSpecifiers = extractSystemFilePackages(resolvedSystemPath);
|
|
605
362
|
packageMap = {};
|
|
363
|
+
externalSourceEntries.clear();
|
|
364
|
+
const collectedPkgDirs = [];
|
|
606
365
|
for (const specifier of packageSpecifiers) try {
|
|
607
366
|
const resolved = await this.resolve(specifier);
|
|
608
367
|
if (resolved && resolved.id) {
|
|
609
368
|
const absPath = resolved.id;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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;
|
|
630
417
|
fileEntries.push({
|
|
631
|
-
path:
|
|
632
|
-
source:
|
|
633
|
-
hash:
|
|
418
|
+
path: relPath,
|
|
419
|
+
source: entrySource,
|
|
420
|
+
hash: entryHash
|
|
634
421
|
});
|
|
635
|
-
if (!isProd &&
|
|
636
|
-
hash:
|
|
637
|
-
source:
|
|
422
|
+
if (!isProd && entryHash) fileCache.set(relPath, {
|
|
423
|
+
hash: entryHash,
|
|
424
|
+
source: entrySource
|
|
638
425
|
});
|
|
639
426
|
}
|
|
427
|
+
collectedPkgDirs.push(dirname(absPath));
|
|
428
|
+
packageMap[specifier] = relPath;
|
|
640
429
|
}
|
|
641
|
-
packageMap[specifier] = relPath;
|
|
642
430
|
}
|
|
643
431
|
} catch {}
|
|
432
|
+
externalPackageDirs = collectedPkgDirs;
|
|
644
433
|
const packageFileCount = fileEntries.length - localFileCount;
|
|
645
434
|
log(`Discovered ${fileEntries.length} files (${packageFileCount} from packages) (${Math.round(performance.now() - t0)}ms)`);
|
|
646
435
|
t0 = performance.now();
|
|
@@ -649,9 +438,11 @@ function animusExtract(options = {}) {
|
|
|
649
438
|
const report = storedManifest.report;
|
|
650
439
|
if (report) {
|
|
651
440
|
log(`Extracted ${report.components_extracted}/${report.components_total} components (${Math.round(performance.now() - t0)}ms)`);
|
|
441
|
+
logTimingWaterfall(storedManifest.timing ?? {});
|
|
652
442
|
log(`Reconciliation: ${report.components_extracted} kept, ${report.variants_eliminated} variants pruned, ${report.states_eliminated} states pruned`);
|
|
653
443
|
const details = report.eliminated_details || [];
|
|
654
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}`);
|
|
655
446
|
else if (d.kind === "variant") warn(`⚠ ${d.component} variant '${d.name}' pruned: ${d.reason}`);
|
|
656
447
|
else if (d.kind === "state") warn(`⚠ ${d.component} state '${d.name}' pruned: ${d.reason}`);
|
|
657
448
|
}
|
|
@@ -660,37 +451,69 @@ function animusExtract(options = {}) {
|
|
|
660
451
|
else if (d.kind === "skip") warn(`⚠ ${d.component}: skipped ${d.message}`);
|
|
661
452
|
log(`CSS: ${resolvedComponentCss.length} bytes (${Object.keys(storedManifest.components || {}).length} components)`);
|
|
662
453
|
if (!isProd && storedSheets) {
|
|
663
|
-
const staticSize = (
|
|
454
|
+
const staticSize = assembleStylesheet({
|
|
455
|
+
layers: options.layers,
|
|
456
|
+
variableCss,
|
|
457
|
+
globalCss
|
|
458
|
+
}).length;
|
|
664
459
|
const componentSize = resolvedComponentCss.length;
|
|
665
460
|
log(`Delivery: split mode — static ${staticSize} bytes, components ${componentSize} bytes (adopted stylesheet)`);
|
|
666
461
|
} else log("Delivery: single file mode (production)");
|
|
667
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();
|
|
668
471
|
},
|
|
669
472
|
resolveId(id) {
|
|
670
473
|
if (id === VIRTUAL_CSS_ID) return RESOLVED_CSS_ID;
|
|
671
474
|
if (id === VIRTUAL_COMPONENTS_ID) return RESOLVED_COMPONENTS_ID;
|
|
672
475
|
if (id === VIRTUAL_BRIDGE_ID) return RESOLVED_BRIDGE_ID;
|
|
673
476
|
if (id === VIRTUAL_SYSTEM_PROPS_ID) return RESOLVED_SYSTEM_PROPS_ID;
|
|
477
|
+
const srcEntry = externalSourceEntries.get(id);
|
|
478
|
+
if (srcEntry) return srcEntry;
|
|
674
479
|
return null;
|
|
675
480
|
},
|
|
676
481
|
load(id) {
|
|
482
|
+
const lcssOpts = {
|
|
483
|
+
minify: options.minify ?? isProd,
|
|
484
|
+
targets: lcssTargets,
|
|
485
|
+
warnFn: warn
|
|
486
|
+
};
|
|
677
487
|
if (id === RESOLVED_CSS_ID) {
|
|
678
|
-
if (!isProd && storedSheets)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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,
|
|
684
502
|
variableCss,
|
|
685
503
|
globalCss,
|
|
686
|
-
resolvedComponentCss
|
|
687
|
-
|
|
504
|
+
componentCss: resolvedComponentCss,
|
|
505
|
+
split: true
|
|
506
|
+
});
|
|
507
|
+
return [variables, postProcessCss(body, lcssOpts)].filter(Boolean).join("\n");
|
|
688
508
|
}
|
|
689
|
-
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, "\\$")}\`;`;
|
|
690
513
|
if (id === RESOLVED_BRIDGE_ID) return `
|
|
691
514
|
import css from '${VIRTUAL_COMPONENTS_ID}';
|
|
692
515
|
|
|
693
|
-
const GLOBAL_KEY = '
|
|
516
|
+
const GLOBAL_KEY = '__animus_sheet_${createHash("md5").update(options.system).digest("hex").slice(0, 8)}__';
|
|
694
517
|
let sheet = globalThis[GLOBAL_KEY] || null;
|
|
695
518
|
|
|
696
519
|
if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
|
|
@@ -734,18 +557,41 @@ if (import.meta.hot) {
|
|
|
734
557
|
...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
|
|
735
558
|
};
|
|
736
559
|
moduleSource += `\nexport const dynamicPropConfig = ${JSON.stringify(configEntries)};`;
|
|
737
|
-
|
|
738
|
-
}
|
|
560
|
+
} else moduleSource += `\nexport const dynamicPropConfig = {};`;
|
|
561
|
+
moduleSource += `\nexport const transforms = ${storedTransformsSource};`;
|
|
739
562
|
return moduleSource;
|
|
740
563
|
}
|
|
741
564
|
return null;
|
|
742
565
|
},
|
|
743
566
|
transform(code, id) {
|
|
744
567
|
if (!storedManifest) return null;
|
|
745
|
-
if (
|
|
746
|
-
|
|
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
|
+
}
|
|
747
572
|
const relativePath = relative(rootDir, id);
|
|
748
|
-
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
|
+
}
|
|
749
595
|
try {
|
|
750
596
|
const { transformFile } = __require("@animus-ui/extract");
|
|
751
597
|
const result = transformFile(code, relativePath, storedManifestJson);
|
|
@@ -762,29 +608,35 @@ if (import.meta.hot) {
|
|
|
762
608
|
map: null
|
|
763
609
|
};
|
|
764
610
|
} catch (e) {
|
|
765
|
-
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 });
|
|
766
612
|
console.warn(`[animus-extract] Failed to transform ${id}:`, e);
|
|
767
613
|
return null;
|
|
768
614
|
}
|
|
769
615
|
},
|
|
770
|
-
|
|
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 }) {
|
|
771
629
|
if (isProd) return;
|
|
772
630
|
const ext = extname(file);
|
|
773
|
-
if (!
|
|
774
|
-
|
|
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;
|
|
775
634
|
const absFile = resolve(file);
|
|
776
635
|
const relPath = relative(rootDir, absFile);
|
|
777
|
-
|
|
778
|
-
const isThemeChange = resolvedThemePath && absFile === resolve(resolvedThemePath);
|
|
779
|
-
const isSystemChange = resolvedSystemPath && absFile === resolve(resolvedSystemPath);
|
|
780
|
-
if (isConfigChange || isThemeChange || isSystemChange) {
|
|
636
|
+
if (resolvedSystemPath && absFile === resolve(resolvedSystemPath)) {
|
|
781
637
|
const resetStart = performance.now();
|
|
782
638
|
log(`HMR geological reset: ${relPath}`);
|
|
783
|
-
|
|
784
|
-
else {
|
|
785
|
-
if (isConfigChange) loadConfig();
|
|
786
|
-
if (isThemeChange) loadTheme();
|
|
787
|
-
}
|
|
639
|
+
loadSystem();
|
|
788
640
|
try {
|
|
789
641
|
const { clearAnalysisCache } = __require("@animus-ui/extract");
|
|
790
642
|
clearAnalysisCache();
|
|
@@ -821,19 +673,47 @@ if (import.meta.hot) {
|
|
|
821
673
|
} catch {
|
|
822
674
|
return;
|
|
823
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
|
+
}
|
|
824
690
|
const hash = contentHash(source);
|
|
825
|
-
const cached = fileCache.get(
|
|
691
|
+
const cached = fileCache.get(scannerRelPath);
|
|
826
692
|
if (cached && cached.hash === hash) {
|
|
827
|
-
log(`HMR skip: ${
|
|
693
|
+
log(`HMR skip: ${scannerRelPath} (unchanged)`);
|
|
828
694
|
return [];
|
|
829
695
|
}
|
|
830
|
-
fileCache.set(
|
|
696
|
+
fileCache.set(scannerRelPath, {
|
|
831
697
|
hash,
|
|
832
698
|
source
|
|
833
699
|
});
|
|
834
700
|
const hmrStart = performance.now();
|
|
835
701
|
const prevReplacements = /* @__PURE__ */ new Map();
|
|
836
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`);
|
|
837
717
|
const analysisStart = performance.now();
|
|
838
718
|
runAnalysis(buildFileEntriesFromCache(fileCache, relPath));
|
|
839
719
|
const analysisMs = Math.round(performance.now() - analysisStart);
|
|
@@ -864,9 +744,10 @@ if (import.meta.hot) {
|
|
|
864
744
|
}
|
|
865
745
|
const hmrMs = Math.round(performance.now() - hmrStart);
|
|
866
746
|
log(`HMR update: ${relPath} — analysis ${analysisMs}ms, ${modulesToUpdate.length - modules.length} modules invalidated, total ${hmrMs}ms`);
|
|
747
|
+
logTimingWaterfall(storedManifest?.timing ?? {});
|
|
867
748
|
if (modulesToUpdate.length > modules.length) return modulesToUpdate;
|
|
868
749
|
}
|
|
869
750
|
};
|
|
870
751
|
}
|
|
871
752
|
//#endregion
|
|
872
|
-
export { animusExtract, animusExtract as default,
|
|
753
|
+
export { animusExtract, animusExtract as default, discoverFiles };
|