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

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