@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/dist/index.mjs CHANGED
@@ -1,113 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
- import { execSync } from "child_process";
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, unlinkSync, writeFileSync } from "fs";
5
- import { tmpdir } from "os";
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 (DEFAULT_EXTENSIONS.has(extname(entry))) results.push(fullPath);
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
- * Apply transform placeholders emitted by the Rust pipeline.
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 applyTransformPlaceholders(css, transformRegistry) {
162
- return css.replace(/__TRANSFORM__(\w+)__(.+?)__/g, (_match, name, rawValue) => {
163
- const fn = transformRegistry.get(name);
164
- if (!fn) {
165
- console.warn(`[animus-extract] Unknown transform: "${name}" — using raw value`);
166
- return rawValue;
167
- }
168
- const result = fn(rawValue !== "" && !isNaN(Number(rawValue)) ? Number(rawValue) : rawValue);
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 properties that accept unitless numeric values.
174
- * Matches @emotion/unitless and React DOM's style handling.
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
- const UNITLESS_PROPERTIES = new Set([
178
- "animation-iteration-count",
179
- "border-image-outset",
180
- "border-image-slice",
181
- "border-image-width",
182
- "box-flex",
183
- "box-flex-group",
184
- "box-ordinal-group",
185
- "column-count",
186
- "columns",
187
- "flex",
188
- "flex-grow",
189
- "flex-positive",
190
- "flex-shrink",
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 transformRegistry = /* @__PURE__ */ new Map();
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 single bun subprocess.
418
- * Replaces loadConfig() + loadTheme() when `system` option is provided.
419
- * The subprocess imports the module and calls .serialize().
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 ts = Date.now();
426
- const tmpScript = join(tmpdir(), `animus-system-${ts}.js`);
427
- const tmpOut = join(tmpdir(), `animus-system-${ts}.json`);
428
- 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();\nconst tokens = m.tokens || m.theme || null;\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify({\n propConfig: cfg.propConfig,\n groupRegistry: cfg.groupRegistry,\n tokens: tokens,\n transformNames: Object.keys(cfg.transforms || {}),\n globalStyles: cfg.globalStyles || null\n}));\n`);
429
- execSync(`bun run "${tmpScript}"`, {
430
- cwd: rootDir,
431
- encoding: "utf-8"
432
- });
433
- const result = readFileSync(tmpOut, "utf-8");
434
- try {
435
- unlinkSync(tmpScript);
436
- unlinkSync(tmpOut);
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
- * Uses fileCache if populated (dev mode), otherwise uses provided entries.
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 manifestJson = analyzeProject(JSON.stringify(fileEntries), themeJson, variableMapJson, configJson, groupRegistryJson, JSON.stringify(packageMap), !isProd);
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
- if (usedTransformNames.size > 0 && transformRegistry.size > 0) {
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
- storedSheets = storedManifest?.sheets ?? null;
526
- const rawCss = storedManifest?.css || "";
527
- const systemResolveScript = globalThis.__animus_system_resolve_script;
528
- if (systemResolveScript && rawCss.includes("__TRANSFORM__") && isProd) try {
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
- else resolvedComponentCss = resolveTransformPlaceholders(rawCss, rootDir, options.configPath, transformRegistry);
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
- if (options.system) loadSystem();
564
- else {
565
- loadConfig();
566
- loadTheme();
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
- const filePaths = discoverFiles(rootDir, rootDir, excludePatterns);
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
- const source = readFileSync(filePath, "utf-8");
579
- const relPath = relative(rootDir, filePath);
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 patterns = options.packagePatterns ?? ["@animus-ui/*"];
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
- const relPath = relative(rootDir, absPath);
611
- if (!fileEntries.some((e) => e.path === relPath)) {
612
- const entrySource = readFileSync(absPath, "utf-8");
613
- const entryHash = !isProd ? contentHash(entrySource) : void 0;
614
- fileEntries.push({
615
- path: relPath,
616
- source: entrySource,
617
- hash: entryHash
618
- });
619
- if (!isProd && entryHash) fileCache.set(relPath, {
620
- hash: entryHash,
621
- source: entrySource
622
- });
623
- }
624
- const pkgFiles = discoverFiles(dirname(absPath), rootDir, excludePatterns);
625
- for (const pkgFile of pkgFiles) {
626
- const pkgRelPath = relative(rootDir, pkgFile);
627
- if (!fileEntries.some((e) => e.path === pkgRelPath)) {
628
- const pkgSource = readFileSync(pkgFile, "utf-8");
629
- const pkgHash = !isProd ? contentHash(pkgSource) : void 0;
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: pkgRelPath,
632
- source: pkgSource,
633
- hash: pkgHash
418
+ path: relPath,
419
+ source: entrySource,
420
+ hash: entryHash
634
421
  });
635
- if (!isProd && pkgHash) fileCache.set(pkgRelPath, {
636
- hash: pkgHash,
637
- source: pkgSource
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 = (storedSheets.declaration + variableCss + globalCss).length;
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) return [
679
- storedSheets.declaration,
680
- variableCss,
681
- globalCss
682
- ].filter(Boolean).join("\n");
683
- return [
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
- ].filter(Boolean).join("\n");
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 || "").replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;`;
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 = '__animus_component_sheet__';
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
- moduleSource += `\nexport const transforms = ${storedTransformsSource};`;
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 (!/\.[jt]sx?$/.test(id)) return null;
746
- if (id.includes("node_modules")) return null;
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) return null;
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
- handleHotUpdate({ file, server: hmrServer, modules }) {
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 (!DEFAULT_EXTENSIONS.has(ext)) return;
774
- if ((options.exclude ?? DEFAULT_EXCLUDE).some((pattern) => file.includes(pattern) || relative(rootDir, file).includes(pattern))) return;
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
- const isConfigChange = resolvedConfigPath && absFile === resolve(resolvedConfigPath);
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
- if (isSystemChange) loadSystem();
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(relPath);
691
+ const cached = fileCache.get(scannerRelPath);
826
692
  if (cached && cached.hash === hash) {
827
- log(`HMR skip: ${relPath} (unchanged)`);
693
+ log(`HMR skip: ${scannerRelPath} (unchanged)`);
828
694
  return [];
829
695
  }
830
- fileCache.set(relPath, {
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, evaluateTheme, evaluateThemeObject };
753
+ export { animusExtract, animusExtract as default, discoverFiles };