@animus-ui/vite-plugin 0.1.0-next.v7 → 0.1.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs DELETED
@@ -1,818 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { execSync } from "child_process";
3
- import { createHash } from "crypto";
4
- import { existsSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
5
- import { tmpdir } from "os";
6
- import { dirname, extname, join, relative, resolve } from "path";
7
- import { fileURLToPath } from "url";
8
- //#region \0rolldown/runtime.js
9
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
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
- //#region src/index.ts
114
- const __pluginDir = dirname(fileURLToPath(import.meta.url));
115
- const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
116
- const RESOLVED_CSS_ID = "\0virtual:animus/styles.css";
117
- const VIRTUAL_COMPONENTS_ID = "virtual:animus/components.js";
118
- const RESOLVED_COMPONENTS_ID = "\0virtual:animus/components.js";
119
- const VIRTUAL_BRIDGE_ID = "virtual:animus/hmr-bridge.js";
120
- const RESOLVED_BRIDGE_ID = "\0virtual:animus/hmr-bridge.js";
121
- const DEFAULT_EXTENSIONS = new Set([
122
- ".ts",
123
- ".tsx",
124
- ".js",
125
- ".jsx"
126
- ]);
127
- const DEFAULT_EXCLUDE = [
128
- "node_modules",
129
- "dist",
130
- ".test.",
131
- ".spec."
132
- ];
133
- function discoverFiles(dir, rootDir, excludePatterns) {
134
- const results = [];
135
- let entries;
136
- try {
137
- entries = readdirSync(dir, { encoding: "utf8" });
138
- } catch {
139
- return results;
140
- }
141
- for (const entry of entries) {
142
- const fullPath = join(dir, entry);
143
- const relativePath = relative(rootDir, fullPath);
144
- if (excludePatterns.some((pattern) => fullPath.includes(pattern) || relativePath.includes(pattern))) continue;
145
- let stat;
146
- try {
147
- stat = statSync(fullPath);
148
- } catch {
149
- continue;
150
- }
151
- if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns));
152
- else if (DEFAULT_EXTENSIONS.has(extname(entry))) results.push(fullPath);
153
- }
154
- return results;
155
- }
156
- /**
157
- * Apply transform placeholders emitted by the Rust pipeline.
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.
162
- */
163
- function applyTransformPlaceholders(css, transformRegistry) {
164
- return css.replace(/__TRANSFORM__(\w+)__(.+?)__/g, (_match, name, rawValue) => {
165
- const fn = transformRegistry.get(name);
166
- if (!fn) {
167
- console.warn(`[animus-extract] Unknown transform: "${name}" — using raw value`);
168
- return rawValue;
169
- }
170
- const result = fn(rawValue !== "" && !isNaN(Number(rawValue)) ? Number(rawValue) : rawValue);
171
- return typeof result === "object" ? JSON.stringify(result) : String(result);
172
- });
173
- }
174
- /**
175
- * CSS properties that accept unitless numeric values.
176
- * Matches @emotion/unitless and React DOM's style handling.
177
- * Bare numerics on properties NOT in this set receive `px`.
178
- */
179
- const UNITLESS_PROPERTIES = new Set([
180
- "animation-iteration-count",
181
- "border-image-outset",
182
- "border-image-slice",
183
- "border-image-width",
184
- "box-flex",
185
- "box-flex-group",
186
- "box-ordinal-group",
187
- "column-count",
188
- "columns",
189
- "flex",
190
- "flex-grow",
191
- "flex-positive",
192
- "flex-shrink",
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
- });
260
- }
261
- /** Compute MD5 content hash for a string. */
262
- function contentHash(source) {
263
- return createHash("md5").update(source).digest("hex");
264
- }
265
- /**
266
- * Reconstruct file entries from cache, including content hashes.
267
- * For unchanged files (hash matches changedPath), sends empty source
268
- * to avoid serializing full source text across the NAPI boundary.
269
- * Rust cache-hit path never reads file.source, so empty string is safe.
270
- */
271
- function buildFileEntriesFromCache(cache, changedPath) {
272
- const entries = [];
273
- for (const [path, { hash, source }] of cache) entries.push({
274
- path,
275
- source: path === changedPath ? source : "",
276
- hash
277
- });
278
- return entries;
279
- }
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 = {}) {
314
- let themeJson = "{}";
315
- let variableMapJson = "{}";
316
- let configJson = "{}";
317
- let groupRegistryJson = "{}";
318
- let isProd = false;
319
- let rootDir = "";
320
- const verbose = options.verbose || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
321
- let logger = null;
322
- function log(msg) {
323
- if (verbose) (logger ?? console).info(`[animus] ${msg}`);
324
- }
325
- function warn(msg) {
326
- (logger ?? console).warn(`[animus] ${msg}`);
327
- }
328
- let variableCss = "";
329
- let globalCss = "";
330
- let transformRegistry = /* @__PURE__ */ new Map();
331
- let storedManifest = null;
332
- let storedManifestJson = "";
333
- let resolvedComponentCss = "";
334
- let storedSheets = null;
335
- const fileCache = /* @__PURE__ */ new Map();
336
- let packageMap = {};
337
- let bridgeInjected = false;
338
- let resolvedConfigPath = null;
339
- let resolvedThemePath = null;
340
- 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
- /**
416
- * Load a SystemInstance via single bun subprocess.
417
- * Replaces loadConfig() + loadTheme() when `system` option is provided.
418
- * The subprocess imports the module and calls .serialize().
419
- */
420
- function loadSystem() {
421
- if (!options.system) return;
422
- resolvedSystemPath = resolve(rootDir, options.system);
423
- try {
424
- const ts = Date.now();
425
- const tmpScript = join(tmpdir(), `animus-system-${ts}.js`);
426
- const tmpOut = join(tmpdir(), `animus-system-${ts}.json`);
427
- writeFileSync(tmpScript, `const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nif (!ds || !ds.serialize) { throw new Error('Module does not export a SystemInstance with .serialize()'); }\nconst cfg = ds.serialize();\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify({\n propConfig: cfg.propConfig,\n groupRegistry: cfg.groupRegistry,\n tokens: cfg.tokens,\n transformNames: Object.keys(cfg.transforms || {}),\n globalStyles: cfg.globalStyles || null\n}));\n`);
428
- execSync(`bun run "${tmpScript}"`, {
429
- cwd: rootDir,
430
- encoding: "utf-8"
431
- });
432
- const result = readFileSync(tmpOut, "utf-8");
433
- try {
434
- unlinkSync(tmpScript);
435
- unlinkSync(tmpOut);
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
- }
482
- } catch (e) {
483
- if (options.strict) throw new Error(`[animus-extract] Failed to load system from ${resolvedSystemPath}: ${e}`);
484
- console.warn(`[animus-extract] Failed to load system from ${resolvedSystemPath}:`, e);
485
- }
486
- }
487
- /**
488
- * Run project analysis and update the manifest.
489
- * Uses fileCache if populated (dev mode), otherwise uses provided entries.
490
- */
491
- function runAnalysis(fileEntries) {
492
- try {
493
- const { analyzeProject } = __require("@animus-ui/extract");
494
- const manifestJson = analyzeProject(JSON.stringify(fileEntries), themeJson, variableMapJson, configJson, groupRegistryJson, JSON.stringify(packageMap), !isProd);
495
- storedManifest = JSON.parse(manifestJson);
496
- storedManifestJson = manifestJson;
497
- bridgeInjected = false;
498
- storedSheets = storedManifest?.sheets ?? null;
499
- const rawCss = storedManifest?.css || "";
500
- const systemResolveScript = globalThis.__animus_system_resolve_script;
501
- if (systemResolveScript && rawCss.includes("__TRANSFORM__") && isProd) try {
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);
518
- }
519
- else resolvedComponentCss = resolveTransformPlaceholders(rawCss, rootDir, options.configPath, transformRegistry);
520
- resolvedComponentCss = applyUnitFallback(resolvedComponentCss);
521
- } catch (e) {
522
- if (options.strict) throw new Error(`[animus-extract] analyzeProject failed: ${e}`);
523
- console.warn("[animus-extract] analyzeProject failed:", e);
524
- }
525
- }
526
- return {
527
- name: "animus-extract",
528
- enforce: "pre",
529
- configResolved(config) {
530
- isProd = config.command === "build";
531
- rootDir = config.root;
532
- logger = config.logger;
533
- },
534
- async buildStart() {
535
- let t0 = performance.now();
536
- if (options.system) loadSystem();
537
- else {
538
- loadConfig();
539
- loadTheme();
540
- }
541
- if (verbose) {
542
- const propCount = Object.keys(JSON.parse(configJson)).length;
543
- const groupCount = Object.keys(JSON.parse(groupRegistryJson)).length;
544
- log(`System loaded: ${propCount} props, ${groupCount} groups (${Math.round(performance.now() - t0)}ms)`);
545
- }
546
- t0 = performance.now();
547
- const excludePatterns = options.exclude ?? DEFAULT_EXCLUDE;
548
- const filePaths = discoverFiles(rootDir, rootDir, excludePatterns);
549
- const fileEntries = [];
550
- for (const filePath of filePaths) try {
551
- const source = readFileSync(filePath, "utf-8");
552
- const relPath = relative(rootDir, filePath);
553
- const hash = !isProd ? contentHash(source) : void 0;
554
- fileEntries.push({
555
- path: relPath,
556
- source,
557
- hash
558
- });
559
- if (!isProd && hash) fileCache.set(relPath, {
560
- hash,
561
- source
562
- });
563
- } catch {}
564
- const localFileCount = fileEntries.length;
565
- const patterns = options.packagePatterns ?? ["@animus-ui/*"];
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
- }
578
- packageMap = {};
579
- for (const specifier of packageSpecifiers) try {
580
- const resolved = await this.resolve(specifier);
581
- if (resolved && resolved.id) {
582
- const absPath = resolved.id;
583
- const relPath = relative(rootDir, absPath);
584
- if (!fileEntries.some((e) => e.path === relPath)) {
585
- const entrySource = readFileSync(absPath, "utf-8");
586
- const entryHash = !isProd ? contentHash(entrySource) : void 0;
587
- fileEntries.push({
588
- path: relPath,
589
- source: entrySource,
590
- hash: entryHash
591
- });
592
- if (!isProd && entryHash) fileCache.set(relPath, {
593
- hash: entryHash,
594
- source: entrySource
595
- });
596
- }
597
- const pkgFiles = discoverFiles(dirname(absPath), rootDir, excludePatterns);
598
- for (const pkgFile of pkgFiles) {
599
- const pkgRelPath = relative(rootDir, pkgFile);
600
- if (!fileEntries.some((e) => e.path === pkgRelPath)) {
601
- const pkgSource = readFileSync(pkgFile, "utf-8");
602
- const pkgHash = !isProd ? contentHash(pkgSource) : void 0;
603
- fileEntries.push({
604
- path: pkgRelPath,
605
- source: pkgSource,
606
- hash: pkgHash
607
- });
608
- if (!isProd && pkgHash) fileCache.set(pkgRelPath, {
609
- hash: pkgHash,
610
- source: pkgSource
611
- });
612
- }
613
- }
614
- packageMap[specifier] = relPath;
615
- }
616
- } catch {}
617
- const packageFileCount = fileEntries.length - localFileCount;
618
- log(`Discovered ${fileEntries.length} files (${packageFileCount} from packages) (${Math.round(performance.now() - t0)}ms)`);
619
- t0 = performance.now();
620
- runAnalysis(fileEntries);
621
- if (storedManifest) {
622
- const report = storedManifest.report;
623
- if (report) {
624
- log(`Extracted ${report.components_extracted}/${report.components_total} components (${Math.round(performance.now() - t0)}ms)`);
625
- log(`Reconciliation: ${report.components_extracted} kept, ${report.variants_eliminated} variants pruned, ${report.states_eliminated} states pruned`);
626
- const details = report.eliminated_details || [];
627
- for (const d of details) if (d.kind === "component") warn(`⚠ ${d.component} eliminated: ${d.reason}`);
628
- else if (d.kind === "variant") warn(`⚠ ${d.component} variant '${d.name}' pruned: ${d.reason}`);
629
- else if (d.kind === "state") warn(`⚠ ${d.component} state '${d.name}' pruned: ${d.reason}`);
630
- }
631
- const diagnostics = storedManifest.diagnostics || [];
632
- for (const d of diagnostics) if (d.kind === "bail") warn(`⚠ ${d.component} not extracted: ${d.message}`);
633
- else if (d.kind === "skip") warn(`⚠ ${d.component}: skipped ${d.message}`);
634
- log(`CSS: ${resolvedComponentCss.length} bytes (${Object.keys(storedManifest.components || {}).length} components)`);
635
- if (!isProd && storedSheets) {
636
- const staticSize = (storedSheets.declaration + variableCss + globalCss).length;
637
- const componentSize = resolvedComponentCss.length;
638
- log(`Delivery: split mode — static ${staticSize} bytes, components ${componentSize} bytes (adopted stylesheet)`);
639
- } else log("Delivery: single file mode (production)");
640
- }
641
- },
642
- resolveId(id) {
643
- if (id === VIRTUAL_CSS_ID) return RESOLVED_CSS_ID;
644
- if (id === VIRTUAL_COMPONENTS_ID) return RESOLVED_COMPONENTS_ID;
645
- if (id === VIRTUAL_BRIDGE_ID) return RESOLVED_BRIDGE_ID;
646
- return null;
647
- },
648
- load(id) {
649
- if (id === RESOLVED_CSS_ID) {
650
- if (!isProd && storedSheets) return [
651
- storedSheets.declaration,
652
- variableCss,
653
- globalCss
654
- ].filter(Boolean).join("\n");
655
- return [
656
- variableCss,
657
- globalCss,
658
- resolvedComponentCss
659
- ].filter(Boolean).join("\n");
660
- }
661
- if (id === RESOLVED_COMPONENTS_ID) return `export default \`${(resolvedComponentCss || "").replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;`;
662
- if (id === RESOLVED_BRIDGE_ID) return `
663
- import css from '${VIRTUAL_COMPONENTS_ID}';
664
-
665
- const GLOBAL_KEY = '__animus_component_sheet__';
666
- let sheet = globalThis[GLOBAL_KEY] || null;
667
-
668
- if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
669
- if (!sheet) {
670
- sheet = new CSSStyleSheet();
671
- globalThis[GLOBAL_KEY] = sheet;
672
- document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
673
- }
674
- sheet.replaceSync(css);
675
- } else {
676
- // Fallback: inject or update <style> tag
677
- let el = document.querySelector('style[data-animus-components]');
678
- if (!el) {
679
- el = document.createElement('style');
680
- el.setAttribute('data-animus-components', '');
681
- document.head.appendChild(el);
682
- }
683
- el.textContent = css;
684
- }
685
-
686
- if (import.meta.hot) {
687
- import.meta.hot.accept('${VIRTUAL_COMPONENTS_ID}', (newModule) => {
688
- if (sheet) {
689
- sheet.replaceSync(newModule.default);
690
- } else {
691
- const el = document.querySelector('style[data-animus-components]');
692
- if (el) el.textContent = newModule.default;
693
- }
694
- });
695
- }
696
- `;
697
- return null;
698
- },
699
- transform(code, id) {
700
- if (!storedManifest) return null;
701
- if (!/\.[jt]sx?$/.test(id)) return null;
702
- if (id.includes("node_modules")) return null;
703
- const relativePath = relative(rootDir, id);
704
- if (!storedManifest.files?.[relativePath]?.length) return null;
705
- try {
706
- const { transformFile } = __require("@animus-ui/extract");
707
- const result = transformFile(code, relativePath, storedManifestJson);
708
- if (!result.hasComponents) return null;
709
- let transformedCode = result.code;
710
- if (!isProd && !bridgeInjected && storedSheets) {
711
- transformedCode = `import '${VIRTUAL_BRIDGE_ID}';\n${transformedCode}`;
712
- bridgeInjected = true;
713
- log("HMR bridge injected via transform");
714
- }
715
- if (verbose) log(`transform ${relativePath}: ${storedManifest.files?.[relativePath]?.length ?? 0} components`);
716
- return {
717
- code: transformedCode,
718
- map: null
719
- };
720
- } catch (e) {
721
- if (options.strict) throw new Error(`[animus-extract] Failed to transform ${id}: ${e}`);
722
- console.warn(`[animus-extract] Failed to transform ${id}:`, e);
723
- return null;
724
- }
725
- },
726
- handleHotUpdate({ file, server: hmrServer, modules }) {
727
- if (isProd) return;
728
- const ext = extname(file);
729
- if (!DEFAULT_EXTENSIONS.has(ext)) return;
730
- if ((options.exclude ?? DEFAULT_EXCLUDE).some((pattern) => file.includes(pattern) || relative(rootDir, file).includes(pattern))) return;
731
- const absFile = resolve(file);
732
- const relPath = relative(rootDir, absFile);
733
- const isConfigChange = resolvedConfigPath && absFile === resolve(resolvedConfigPath);
734
- const isThemeChange = resolvedThemePath && absFile === resolve(resolvedThemePath);
735
- const isSystemChange = resolvedSystemPath && absFile === resolve(resolvedSystemPath);
736
- if (isConfigChange || isThemeChange || isSystemChange) {
737
- const resetStart = performance.now();
738
- log(`HMR geological reset: ${relPath}`);
739
- if (isSystemChange) loadSystem();
740
- else {
741
- if (isConfigChange) loadConfig();
742
- if (isThemeChange) loadTheme();
743
- }
744
- try {
745
- const { clearAnalysisCache } = __require("@animus-ui/extract");
746
- clearAnalysisCache();
747
- } catch {}
748
- const fileEntries = [];
749
- for (const [path, { hash, source }] of fileCache) fileEntries.push({
750
- path,
751
- source,
752
- hash
753
- });
754
- runAnalysis(fileEntries);
755
- log(`HMR geological reset complete: ${Math.round(performance.now() - resetStart)}ms`);
756
- const geologicalModules = [...modules];
757
- const cssModule = hmrServer.moduleGraph.getModuleById(RESOLVED_CSS_ID);
758
- if (cssModule) {
759
- hmrServer.moduleGraph.invalidateModule(cssModule);
760
- geologicalModules.push(cssModule);
761
- }
762
- const compModule = hmrServer.moduleGraph.getModuleById(RESOLVED_COMPONENTS_ID);
763
- if (compModule) {
764
- hmrServer.moduleGraph.invalidateModule(compModule);
765
- geologicalModules.push(compModule);
766
- }
767
- return geologicalModules;
768
- }
769
- let source;
770
- try {
771
- source = readFileSync(absFile, "utf-8");
772
- } catch {
773
- return;
774
- }
775
- const hash = contentHash(source);
776
- const cached = fileCache.get(relPath);
777
- if (cached && cached.hash === hash) {
778
- log(`HMR skip: ${relPath} (unchanged)`);
779
- return [];
780
- }
781
- fileCache.set(relPath, {
782
- hash,
783
- source
784
- });
785
- const hmrStart = performance.now();
786
- const prevReplacements = /* @__PURE__ */ new Map();
787
- if (storedManifest?.components) for (const [id, desc] of Object.entries(storedManifest.components)) prevReplacements.set(id, desc.replacement ?? "");
788
- const analysisStart = performance.now();
789
- runAnalysis(buildFileEntriesFromCache(fileCache, relPath));
790
- const analysisMs = Math.round(performance.now() - analysisStart);
791
- const modulesToUpdate = [...modules];
792
- const compModule = hmrServer.moduleGraph.getModuleById(RESOLVED_COMPONENTS_ID);
793
- if (compModule) {
794
- hmrServer.moduleGraph.invalidateModule(compModule);
795
- modulesToUpdate.push(compModule);
796
- }
797
- if (storedManifest?.components) {
798
- const staleFiles = /* @__PURE__ */ new Set();
799
- for (const [id, desc] of Object.entries(storedManifest.components)) if ((desc.replacement ?? "") !== (prevReplacements.get(id) ?? "")) staleFiles.add(desc.file);
800
- for (const defFile of staleFiles) {
801
- const absDefPath = resolve(rootDir, defFile);
802
- if (absDefPath === absFile) continue;
803
- const defModule = hmrServer.moduleGraph.getModuleById(absDefPath) ?? hmrServer.moduleGraph.getModulesByFile(absDefPath)?.values().next().value;
804
- if (defModule) {
805
- log(`HMR invalidate: ${defFile} (replacement changed)`);
806
- hmrServer.moduleGraph.invalidateModule(defModule);
807
- modulesToUpdate.push(defModule);
808
- }
809
- }
810
- }
811
- const hmrMs = Math.round(performance.now() - hmrStart);
812
- log(`HMR update: ${relPath} — analysis ${analysisMs}ms, ${modulesToUpdate.length - modules.length} modules invalidated, total ${hmrMs}ms`);
813
- if (modulesToUpdate.length > modules.length) return modulesToUpdate;
814
- }
815
- };
816
- }
817
- //#endregion
818
- export { animusExtract, animusExtract as default, evaluateTheme, evaluateThemeObject };