@animus-ui/next-plugin 0.1.0-next.48 → 0.1.0-next.58

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.cjs CHANGED
@@ -4,7 +4,6 @@ let fs = require("fs");
4
4
  let path = require("path");
5
5
  let url = require("url");
6
6
  let crypto = require("crypto");
7
- let os = require("os");
8
7
  let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
9
8
  //#region src/plugin.ts
10
9
  const PLUGIN_NAME = "AnimusWebpackPlugin";
@@ -17,8 +16,11 @@ var AnimusWebpackPlugin = class {
17
16
  variableMapJson = "";
18
17
  variableCss = "";
19
18
  contextualVarsJson = null;
19
+ selectorAliasesJson = null;
20
+ selectorOrderJson = null;
20
21
  globalCss = "";
21
- systemResolveScript = null;
22
+ globalStyleBlocksJson = null;
23
+ pathAliasesJson = null;
22
24
  fileCache = /* @__PURE__ */ new Map();
23
25
  lastCssHash = null;
24
26
  externalPackageDirs = [];
@@ -26,10 +28,99 @@ var AnimusWebpackPlugin = class {
26
28
  constructor(options) {
27
29
  this.options = options;
28
30
  }
31
+ get verbose() {
32
+ return this.options.verbose === true || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
33
+ }
34
+ log(msg) {
35
+ if (this.verbose) console.info(`[animus] ${msg}`);
36
+ }
37
+ now() {
38
+ return this.verbose ? performance.now() : 0;
39
+ }
40
+ elapsed(t) {
41
+ return this.verbose ? Math.round(performance.now() - t) : 0;
42
+ }
43
+ logBuildTimings(bt, rustTiming) {
44
+ if (!this.verbose) return;
45
+ for (const [key, label] of [
46
+ ["systemLoad", "system-load"],
47
+ ["fileDiscovery", "file-discovery"],
48
+ ["fileRead", "file-read+hash"],
49
+ ["packageResolve", "pkg-resolve"],
50
+ ["analysis", "analysis"]
51
+ ]) {
52
+ const ms = bt[key] ?? 0;
53
+ if (ms === 0 && !Object.hasOwn(bt, key)) continue;
54
+ const pad = " ".repeat(Math.max(0, 17 - label.length));
55
+ this.log(` ${label}${pad}${String(ms).padStart(5)}ms`);
56
+ if (key === "analysis") for (const [sk, sl] of [
57
+ ["jsonSerialize", "json-serialize"],
58
+ ["rustExtract", "rust-extract"],
59
+ ["jsonParse", "json-parse"]
60
+ ]) {
61
+ const sms = bt[sk] ?? 0;
62
+ const spad = " ".repeat(Math.max(0, 15 - sl.length));
63
+ this.log(` ${sl}${spad}${String(sms).padStart(5)}ms`);
64
+ if (sk === "rustExtract" && rustTiming) for (const [rk, rl] of [
65
+ ["parseAndWalk", "parse+walk"],
66
+ ["importResolution", "imports"],
67
+ ["extensionProvenance", "provenance"],
68
+ ["topologicalSort", "topo-sort"],
69
+ ["chainEvaluation", "chains"],
70
+ ["jsxScanning", "jsx-scan"],
71
+ ["systemPropAggregation", "sys-props"],
72
+ ["usageLedger", "usage"],
73
+ ["reconciliation", "reconcile"],
74
+ ["cssGeneration", "css-gen"],
75
+ ["manifestSerialization", "serialize"]
76
+ ]) {
77
+ const rms = rustTiming[rk] ?? 0;
78
+ const rpad = " ".repeat(Math.max(0, 13 - rl.length));
79
+ const rextra = rk === "parseAndWalk" ? ` (${rustTiming.fileCount ?? 0} files, ${rustTiming.cacheHits ?? 0} cached)` : "";
80
+ this.log(` ${rl}${rpad}${String(rms).padStart(5)}ms${rextra}`);
81
+ }
82
+ }
83
+ }
84
+ this.log(` total ${String(bt.total).padStart(5)}ms`);
85
+ if (process.env.ANIMUS_TIMING_JSON === "1") {
86
+ const merged = {};
87
+ for (const [k, v] of Object.entries(bt)) merged[`buildStart.${k}`] = v;
88
+ if (rustTiming) {
89
+ for (const [k, v] of Object.entries(rustTiming)) if (typeof v === "number") merged[`rust.${k}`] = v;
90
+ }
91
+ console.info(`[animus:timing] ${JSON.stringify(merged)}`);
92
+ }
93
+ }
29
94
  initialized = false;
95
+ /** Extract path aliases from webpack's resolve.alias config. */
96
+ extractAliases(compiler) {
97
+ const rootDir = compiler.context;
98
+ const rawAlias = compiler.options?.resolve?.alias;
99
+ if (!rawAlias || typeof rawAlias !== "object") return;
100
+ const entries = [];
101
+ for (const [key, value] of Object.entries(rawAlias)) {
102
+ const target = Array.isArray(value) ? value[0] : value;
103
+ if (typeof target !== "string") continue;
104
+ if (key.includes(".animus")) continue;
105
+ const replacement = target.startsWith(rootDir) ? target.slice(rootDir.length + 1) : target;
106
+ if (/\.\w+$/.test(replacement)) entries.push({
107
+ pattern: key,
108
+ replacement,
109
+ type: "exact"
110
+ });
111
+ else entries.push({
112
+ pattern: key.endsWith("/") ? key : key + "/",
113
+ replacement: replacement.endsWith("/") ? replacement : replacement + "/",
114
+ type: "prefix"
115
+ });
116
+ }
117
+ entries.sort((a, b) => b.pattern.length - a.pattern.length);
118
+ if (entries.length > 0) this.pathAliasesJson = JSON.stringify({ aliases: entries });
119
+ }
30
120
  apply(compiler) {
31
121
  compiler.hooks.run.tapPromise(PLUGIN_NAME, async (_compiler) => {
32
122
  this.rootDir = _compiler.context;
123
+ this.extractAliases(_compiler);
33
124
  const existing = require_singleton.getAnalysisPromise();
34
125
  if (existing) {
35
126
  await existing;
@@ -41,6 +132,7 @@ var AnimusWebpackPlugin = class {
41
132
  });
42
133
  compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async (_compiler) => {
43
134
  this.rootDir = _compiler.context;
135
+ this.extractAliases(_compiler);
44
136
  if (!this.initialized) {
45
137
  const existing = require_singleton.getAnalysisPromise();
46
138
  if (existing) {
@@ -84,31 +176,41 @@ var AnimusWebpackPlugin = class {
84
176
  ".animus"
85
177
  ];
86
178
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
87
- let hasChanges = false;
179
+ const changedPaths = [];
88
180
  for (const filePath of files) {
89
181
  const relPath = (0, path.relative)(rootDir, filePath);
90
182
  const cached = this.fileCache.get(relPath);
91
183
  const source = (0, fs.readFileSync)(filePath, "utf-8");
92
184
  const hash = (0, crypto.createHash)("md5").update(source).digest("hex");
93
185
  if (!cached || cached.hash !== hash) {
94
- hasChanges = true;
186
+ changedPaths.push(relPath);
95
187
  this.fileCache.set(relPath, {
96
188
  hash,
97
189
  source
98
190
  });
99
191
  }
100
192
  }
101
- if (hasChanges) {
193
+ if (changedPaths.length > 0) {
194
+ const fileEntries = this.buildFileEntriesFromCache(changedPaths);
102
195
  require_singleton.resetAnalysisPromise();
103
- const promise = this.runFullPipeline();
196
+ const promise = this.runIncrementalPipeline(fileEntries);
104
197
  require_singleton.setAnalysisPromise(promise);
105
198
  await promise;
106
199
  }
107
200
  }
108
201
  async runFullPipeline() {
202
+ const pipelineStart = this.now();
203
+ const bt = {};
204
+ try {
205
+ const { clearAnalysisCache } = require("@animus-ui/extract");
206
+ clearAnalysisCache();
207
+ } catch {}
109
208
  const rootDir = this.rootDir;
110
209
  const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
210
+ let t = this.now();
111
211
  this.loadSystem(rootDir, resolvedSystemPath);
212
+ bt.systemLoad = this.elapsed(t);
213
+ t = this.now();
112
214
  const excludePatterns = this.options.exclude ?? [
113
215
  "node_modules",
114
216
  "dist",
@@ -118,6 +220,8 @@ var AnimusWebpackPlugin = class {
118
220
  ".animus"
119
221
  ];
120
222
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
223
+ bt.fileDiscovery = this.elapsed(t);
224
+ t = this.now();
121
225
  const fileEntries = [];
122
226
  for (const filePath of files) {
123
227
  const source = (0, fs.readFileSync)(filePath, "utf-8");
@@ -133,6 +237,9 @@ var AnimusWebpackPlugin = class {
133
237
  hash
134
238
  });
135
239
  }
240
+ bt.fileRead = this.elapsed(t);
241
+ bt.fileCount = fileEntries.length;
242
+ t = this.now();
136
243
  const packageNames = (0, _animus_ui_extract_pipeline.extractSystemFilePackages)(resolvedSystemPath);
137
244
  const packageMap = this.resolvePackagesByName(rootDir, packageNames);
138
245
  const excludePatternsForPkgs = [
@@ -174,6 +281,7 @@ var AnimusWebpackPlugin = class {
174
281
  } else collectedPkgDirs.push((0, path.dirname)(absEntry));
175
282
  }
176
283
  this.externalPackageDirs = collectedPkgDirs;
284
+ bt.packageResolve = this.elapsed(t);
177
285
  const { analyzeProject } = require("@animus-ui/extract");
178
286
  const animusDirPath = (0, path.join)(rootDir, ".animus");
179
287
  const emitterConfig = JSON.stringify({
@@ -181,10 +289,20 @@ var AnimusWebpackPlugin = class {
181
289
  css_module_id: ".animus/styles.css",
182
290
  system_props_module_id: (0, path.join)(animusDirPath, "system-props.js")
183
291
  });
184
- const manifestJson = analyzeProject(JSON.stringify(fileEntries), this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, JSON.stringify(packageMap), false, this.options.prefix || null, emitterConfig);
292
+ t = this.now();
293
+ const fileEntriesJson = JSON.stringify(fileEntries);
294
+ const packageMapJson = JSON.stringify(packageMap);
295
+ bt.jsonSerialize = this.elapsed(t);
296
+ t = this.now();
297
+ const manifestJson = analyzeProject(fileEntriesJson, this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, packageMapJson, false, emitterConfig, this.selectorAliasesJson, this.selectorOrderJson, this.globalStyleBlocksJson, this.pathAliasesJson);
298
+ bt.rustExtract = this.elapsed(t);
299
+ t = this.now();
185
300
  const manifest = JSON.parse(manifestJson);
301
+ bt.jsonParse = this.elapsed(t);
302
+ bt.analysis = (bt.jsonSerialize ?? 0) + (bt.rustExtract ?? 0) + (bt.jsonParse ?? 0);
303
+ if (manifest?.report) this.log(`Extracted ${manifest.report.components_extracted ?? "?"}/${manifest.report.components_total ?? "?"} components (${bt.analysis}ms)`);
304
+ this.globalCss = manifest?.sheets?.global || "";
186
305
  let componentCss = manifest?.css || "";
187
- if (this.systemResolveScript && componentCss.includes("__TRANSFORM__")) componentCss = this.resolveTransforms(rootDir, componentCss);
188
306
  componentCss = (0, _animus_ui_extract_pipeline.applyUnitFallback)(componentCss);
189
307
  const fullCss = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
190
308
  layers: this.options.layers,
@@ -208,87 +326,33 @@ var AnimusWebpackPlugin = class {
208
326
  ...meta.transform_name ? { transformName: meta.transform_name } : {},
209
327
  ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
210
328
  };
211
- let transformsSource = "{}";
212
- if (this.systemResolveScript) try {
213
- const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
214
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-transforms-${Date.now()}.json`);
215
- (0, _animus_ui_extract_pipeline.execSubprocess)(`const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.toConfig();\nconst out = {};\nfor (const [name, fn] of Object.entries(cfg.transforms || {})) {\n out[name] = fn.toString();\n}\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify(out));\n`, rootDir);
216
- const fnStrings = JSON.parse((0, fs.readFileSync)(tmpOut, "utf-8"));
217
- try {
218
- require("fs").unlinkSync(tmpOut);
219
- } catch {}
220
- transformsSource = `{\n${Object.entries(fnStrings).map(([name, src]) => ` ${JSON.stringify(name)}: ${src}`).join(",\n")}\n}`;
221
- } catch {}
329
+ const transformsSource = "{}";
222
330
  const animusDir = (0, path.join)(rootDir, ".animus");
223
331
  if (!(0, fs.existsSync)(animusDir)) (0, fs.mkdirSync)(animusDir, { recursive: true });
224
332
  (0, fs.writeFileSync)((0, path.join)(animusDir, "system-props.js"), `export const systemPropMap = ${systemPropMap};\nexport const systemPropGroups = ${this.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${transformsSource};\n`);
225
333
  require_singleton.setManifestJson(manifestJson);
334
+ bt.total = this.elapsed(pipelineStart);
335
+ this.logBuildTimings(bt, manifest?.timing);
226
336
  }
227
337
  loadSystem(rootDir, systemPath) {
228
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-system-${Date.now()}.json`);
229
- (0, _animus_ui_extract_pipeline.execSubprocess)(`const m = require(${JSON.stringify(systemPath)});\nconst exportNames = Object.keys(m);\nconst candidates = exportNames.filter(k => m[k] && typeof m[k].toConfig === 'function');\nif (candidates.length === 0) {\n throw new Error('[animus-extract] No SystemInstance found. Exports: [' + exportNames.join(', ') + ']. None have a .toConfig() method. A SystemInstance is created by: export const ds = createSystem().build()');\n}\nif (candidates.length > 1) {\n throw new Error('[animus-extract] Multiple SystemInstance candidates found: [' + candidates.join(', ') + ']. Specify which one to use.');\n}\nconst ds = m[candidates[0]];\nconst cfg = ds.toConfig();\nconst tokens = m.tokens || m.theme || null;\nconst serialized = tokens && typeof tokens.serialize === 'function' ? tokens.serialize() : null;\nconst globalStyleBlocks = {};\nfor (const [key, val] of Object.entries(m)) {\n if (val && typeof val === 'object' && val.__brand === 'GlobalStyleBlock') {\n globalStyleBlocks[key] = val.styles;\n }\n}\nconst transformSources = {};\nfor (const [name, fn] of Object.entries(cfg.transforms || {})) {\n transformSources[name] = fn.toString();\n}\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify({\n propConfig: cfg.propConfig,\n groupRegistry: cfg.groupRegistry,\n serialized: serialized,\n transformNames: Object.keys(cfg.transforms || {}),\n transformSources: transformSources,\n globalStyleBlocks: Object.keys(globalStyleBlocks).length > 0 ? globalStyleBlocks : null\n}));\n`, rootDir);
230
- const result = (0, fs.readFileSync)(tmpOut, "utf-8");
231
- try {
232
- require("fs").unlinkSync(tmpOut);
233
- } catch {}
234
- const parsed = JSON.parse(result);
235
- this.configJson = parsed.propConfig;
236
- this.groupRegistryJson = parsed.groupRegistry;
237
- if (parsed.serialized) {
238
- this.themeJson = parsed.serialized.scalesJson;
239
- this.variableMapJson = parsed.serialized.variableMapJson;
240
- this.variableCss = parsed.serialized.variableCss;
241
- this.contextualVarsJson = parsed.serialized.contextualVarsJson;
242
- if (this.options.prefix) {
243
- const prefixed = (0, _animus_ui_extract_pipeline.applyPrefix)(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
244
- this.variableMapJson = prefixed.variableMapJson;
245
- this.variableCss = prefixed.variableCss;
246
- if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
247
- if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
248
- }
249
- } else throw new Error("[animus-extract] Theme must be built with createTheme().build(). No .serialize() method found on tokens export.");
250
- if (parsed.globalStyleBlocks) {
251
- if (Object.keys(parsed.globalStyleBlocks).length > 0) try {
252
- const hasTransforms = parsed.transformNames && parsed.transformNames.length > 0;
253
- const flat = JSON.parse(this.themeJson);
254
- const propConfig = JSON.parse(this.configJson);
255
- const variableMap = {};
256
- for (const [tokenPath, value] of Object.entries(flat)) if (typeof value === "string" && value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
257
- const transforms = {};
258
- if (hasTransforms && parsed.transformSources) for (const [name, src] of Object.entries(parsed.transformSources)) try {
259
- transforms[name] = new Function(`return (${src})`)();
260
- } catch {}
261
- const gsResult = (0, _animus_ui_extract_pipeline.resolveGlobalStyles)(parsed.globalStyleBlocks, propConfig, flat, variableMap, transforms);
262
- const parts = Object.values(gsResult).filter(Boolean);
263
- if (parts.length > 0) this.globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
264
- } catch (e) {
265
- const msg = e instanceof Error ? e.message : String(e);
266
- if (this.options.strict) throw new Error(`[animus-extract] Global styles resolution failed: ${msg}`);
267
- console.warn("[animus-extract] Global styles resolution failed:", msg);
268
- }
269
- }
270
- if (parsed.transformNames && parsed.transformNames.length > 0) {
271
- const tmpResolve = (0, path.join)((0, os.tmpdir)(), `animus-transforms-resolve-${Date.now()}.js`);
272
- (0, fs.writeFileSync)(tmpResolve, `const m = require(${JSON.stringify(systemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.toConfig();\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`);
273
- this.systemResolveScript = tmpResolve;
338
+ const { loadSystemModule } = require("@animus-ui/extract");
339
+ const config = loadSystemModule(systemPath, rootDir);
340
+ this.configJson = config.propConfig;
341
+ this.groupRegistryJson = config.groupRegistry;
342
+ this.themeJson = config.scalesJson;
343
+ this.variableMapJson = config.variableMapJson;
344
+ this.variableCss = config.variableCss;
345
+ this.contextualVarsJson = config.contextualVarsJson;
346
+ this.selectorAliasesJson = config.selectorAliases || null;
347
+ this.selectorOrderJson = config.selectorOrder || null;
348
+ if (this.options.prefix) {
349
+ const prefixed = (0, _animus_ui_extract_pipeline.applyPrefix)(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
350
+ this.variableMapJson = prefixed.variableMapJson;
351
+ this.variableCss = prefixed.variableCss;
352
+ if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
353
+ if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
274
354
  }
275
- }
276
- resolveTransforms(rootDir, css) {
277
- const { execSync } = require("child_process");
278
- const ts = Date.now();
279
- const tmpIn = (0, path.join)((0, os.tmpdir)(), `animus-css-${ts}.css`);
280
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-css-${ts}.out.css`);
281
- (0, fs.writeFileSync)(tmpIn, css);
282
- execSync(`${(0, _animus_ui_extract_pipeline.detectRuntime)()} run "${this.systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
283
- cwd: rootDir,
284
- encoding: "utf-8"
285
- });
286
- const resolved = (0, fs.readFileSync)(tmpOut, "utf-8");
287
- try {
288
- require("fs").unlinkSync(tmpIn);
289
- require("fs").unlinkSync(tmpOut);
290
- } catch {}
291
- return resolved;
355
+ this.globalStyleBlocksJson = config.globalStyleBlocks || null;
292
356
  }
293
357
  discoverFiles(dir, rootDir, excludePatterns) {
294
358
  const results = [];
@@ -364,6 +428,64 @@ var AnimusWebpackPlugin = class {
364
428
  clearAnalysisCache();
365
429
  } catch {}
366
430
  }
431
+ /**
432
+ * Build cache-aware file entries: full source for changed files,
433
+ * empty source + hash for unchanged (Rust cache-hit path skips these).
434
+ */
435
+ buildFileEntriesFromCache(changedPaths) {
436
+ const changedSet = new Set(changedPaths);
437
+ const entries = [];
438
+ for (const [path$1, { hash, source }] of this.fileCache) entries.push({
439
+ path: path$1,
440
+ source: changedSet.has(path$1) ? source : "",
441
+ hash
442
+ });
443
+ return entries;
444
+ }
445
+ /**
446
+ * Run incremental pipeline with cache-aware file entries.
447
+ * Reuses system config from the last full pipeline run.
448
+ */
449
+ async runIncrementalPipeline(fileEntries) {
450
+ const rootDir = this.rootDir;
451
+ const bt = {};
452
+ const pipelineStart = this.now();
453
+ const { analyzeProject } = require("@animus-ui/extract");
454
+ const animusDirPath = (0, path.join)(rootDir, ".animus");
455
+ const emitterConfig = JSON.stringify({
456
+ runtime_import: "@animus-ui/system/runtime",
457
+ css_module_id: (0, path.join)(animusDirPath, "system-props.js")
458
+ });
459
+ const packageMapJson = JSON.stringify(Object.fromEntries(Array.from(this.externalSourceEntries).map(([spec, entry]) => [spec, (0, path.relative)(rootDir, entry)])));
460
+ let t = this.now();
461
+ const fileEntriesJson = JSON.stringify(fileEntries);
462
+ bt.jsonSerialize = this.elapsed(t);
463
+ t = this.now();
464
+ const manifestJson = analyzeProject(fileEntriesJson, this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, packageMapJson, false, emitterConfig, this.selectorAliasesJson, this.selectorOrderJson, this.globalStyleBlocksJson, this.pathAliasesJson);
465
+ bt.rustExtract = this.elapsed(t);
466
+ t = this.now();
467
+ const manifest = JSON.parse(manifestJson);
468
+ bt.jsonParse = this.elapsed(t);
469
+ this.globalCss = manifest?.sheets?.global || "";
470
+ let componentCss = manifest?.css || "";
471
+ componentCss = (0, _animus_ui_extract_pipeline.applyUnitFallback)(componentCss);
472
+ const fullCss = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
473
+ layers: this.options.layers,
474
+ variableCss: this.variableCss,
475
+ globalCss: this.globalCss,
476
+ componentCss
477
+ });
478
+ const cssHash = (0, crypto.createHash)("md5").update(fullCss).digest("hex");
479
+ if (cssHash !== this.lastCssHash) {
480
+ const animusDir = (0, path.join)(rootDir, ".animus");
481
+ if (!(0, fs.existsSync)(animusDir)) (0, fs.mkdirSync)(animusDir, { recursive: true });
482
+ (0, fs.writeFileSync)((0, path.join)(animusDir, "styles.css"), fullCss);
483
+ this.lastCssHash = cssHash;
484
+ }
485
+ require_singleton.setManifestJson(manifestJson);
486
+ bt.total = this.elapsed(pipelineStart);
487
+ this.logBuildTimings(bt, manifest?.timing);
488
+ }
367
489
  /** Expose file cache for HMR change detection */
368
490
  getFileCache() {
369
491
  return this.fileCache;
@@ -430,6 +552,7 @@ function withAnimus(options) {
430
552
  compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
431
553
  nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
432
554
  if (resolveData.request === "virtual:animus/system-props") resolveData.request = systemPropsPath;
555
+ if (resolveData.request === "virtual:animus/styles.css") resolveData.request = (0, path.join)(rootDir, ".animus", "styles.css");
433
556
  const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
434
557
  if (srcEntry) resolveData.request = srcEntry;
435
558
  });
package/dist/index.mjs CHANGED
@@ -3,8 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
3
3
  import { dirname, extname, join, relative, resolve, sep } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { createHash } from "crypto";
6
- import { tmpdir } from "os";
7
- import { applyPrefix, applyUnitFallback, assembleStylesheet, detectRuntime, execSubprocess, extractSystemFilePackages, resolveGlobalStyles } from "@animus-ui/extract/pipeline";
6
+ import { applyPrefix, applyUnitFallback, assembleStylesheet, extractSystemFilePackages } from "@animus-ui/extract/pipeline";
8
7
  //#region src/plugin.ts
9
8
  const PLUGIN_NAME = "AnimusWebpackPlugin";
10
9
  var AnimusWebpackPlugin = class {
@@ -16,8 +15,11 @@ var AnimusWebpackPlugin = class {
16
15
  variableMapJson = "";
17
16
  variableCss = "";
18
17
  contextualVarsJson = null;
18
+ selectorAliasesJson = null;
19
+ selectorOrderJson = null;
19
20
  globalCss = "";
20
- systemResolveScript = null;
21
+ globalStyleBlocksJson = null;
22
+ pathAliasesJson = null;
21
23
  fileCache = /* @__PURE__ */ new Map();
22
24
  lastCssHash = null;
23
25
  externalPackageDirs = [];
@@ -25,10 +27,99 @@ var AnimusWebpackPlugin = class {
25
27
  constructor(options) {
26
28
  this.options = options;
27
29
  }
30
+ get verbose() {
31
+ return this.options.verbose === true || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
32
+ }
33
+ log(msg) {
34
+ if (this.verbose) console.info(`[animus] ${msg}`);
35
+ }
36
+ now() {
37
+ return this.verbose ? performance.now() : 0;
38
+ }
39
+ elapsed(t) {
40
+ return this.verbose ? Math.round(performance.now() - t) : 0;
41
+ }
42
+ logBuildTimings(bt, rustTiming) {
43
+ if (!this.verbose) return;
44
+ for (const [key, label] of [
45
+ ["systemLoad", "system-load"],
46
+ ["fileDiscovery", "file-discovery"],
47
+ ["fileRead", "file-read+hash"],
48
+ ["packageResolve", "pkg-resolve"],
49
+ ["analysis", "analysis"]
50
+ ]) {
51
+ const ms = bt[key] ?? 0;
52
+ if (ms === 0 && !Object.hasOwn(bt, key)) continue;
53
+ const pad = " ".repeat(Math.max(0, 17 - label.length));
54
+ this.log(` ${label}${pad}${String(ms).padStart(5)}ms`);
55
+ if (key === "analysis") for (const [sk, sl] of [
56
+ ["jsonSerialize", "json-serialize"],
57
+ ["rustExtract", "rust-extract"],
58
+ ["jsonParse", "json-parse"]
59
+ ]) {
60
+ const sms = bt[sk] ?? 0;
61
+ const spad = " ".repeat(Math.max(0, 15 - sl.length));
62
+ this.log(` ${sl}${spad}${String(sms).padStart(5)}ms`);
63
+ if (sk === "rustExtract" && rustTiming) for (const [rk, rl] of [
64
+ ["parseAndWalk", "parse+walk"],
65
+ ["importResolution", "imports"],
66
+ ["extensionProvenance", "provenance"],
67
+ ["topologicalSort", "topo-sort"],
68
+ ["chainEvaluation", "chains"],
69
+ ["jsxScanning", "jsx-scan"],
70
+ ["systemPropAggregation", "sys-props"],
71
+ ["usageLedger", "usage"],
72
+ ["reconciliation", "reconcile"],
73
+ ["cssGeneration", "css-gen"],
74
+ ["manifestSerialization", "serialize"]
75
+ ]) {
76
+ const rms = rustTiming[rk] ?? 0;
77
+ const rpad = " ".repeat(Math.max(0, 13 - rl.length));
78
+ const rextra = rk === "parseAndWalk" ? ` (${rustTiming.fileCount ?? 0} files, ${rustTiming.cacheHits ?? 0} cached)` : "";
79
+ this.log(` ${rl}${rpad}${String(rms).padStart(5)}ms${rextra}`);
80
+ }
81
+ }
82
+ }
83
+ this.log(` total ${String(bt.total).padStart(5)}ms`);
84
+ if (process.env.ANIMUS_TIMING_JSON === "1") {
85
+ const merged = {};
86
+ for (const [k, v] of Object.entries(bt)) merged[`buildStart.${k}`] = v;
87
+ if (rustTiming) {
88
+ for (const [k, v] of Object.entries(rustTiming)) if (typeof v === "number") merged[`rust.${k}`] = v;
89
+ }
90
+ console.info(`[animus:timing] ${JSON.stringify(merged)}`);
91
+ }
92
+ }
28
93
  initialized = false;
94
+ /** Extract path aliases from webpack's resolve.alias config. */
95
+ extractAliases(compiler) {
96
+ const rootDir = compiler.context;
97
+ const rawAlias = compiler.options?.resolve?.alias;
98
+ if (!rawAlias || typeof rawAlias !== "object") return;
99
+ const entries = [];
100
+ for (const [key, value] of Object.entries(rawAlias)) {
101
+ const target = Array.isArray(value) ? value[0] : value;
102
+ if (typeof target !== "string") continue;
103
+ if (key.includes(".animus")) continue;
104
+ const replacement = target.startsWith(rootDir) ? target.slice(rootDir.length + 1) : target;
105
+ if (/\.\w+$/.test(replacement)) entries.push({
106
+ pattern: key,
107
+ replacement,
108
+ type: "exact"
109
+ });
110
+ else entries.push({
111
+ pattern: key.endsWith("/") ? key : key + "/",
112
+ replacement: replacement.endsWith("/") ? replacement : replacement + "/",
113
+ type: "prefix"
114
+ });
115
+ }
116
+ entries.sort((a, b) => b.pattern.length - a.pattern.length);
117
+ if (entries.length > 0) this.pathAliasesJson = JSON.stringify({ aliases: entries });
118
+ }
29
119
  apply(compiler) {
30
120
  compiler.hooks.run.tapPromise(PLUGIN_NAME, async (_compiler) => {
31
121
  this.rootDir = _compiler.context;
122
+ this.extractAliases(_compiler);
32
123
  const existing = getAnalysisPromise();
33
124
  if (existing) {
34
125
  await existing;
@@ -40,6 +131,7 @@ var AnimusWebpackPlugin = class {
40
131
  });
41
132
  compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async (_compiler) => {
42
133
  this.rootDir = _compiler.context;
134
+ this.extractAliases(_compiler);
43
135
  if (!this.initialized) {
44
136
  const existing = getAnalysisPromise();
45
137
  if (existing) {
@@ -83,31 +175,41 @@ var AnimusWebpackPlugin = class {
83
175
  ".animus"
84
176
  ];
85
177
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
86
- let hasChanges = false;
178
+ const changedPaths = [];
87
179
  for (const filePath of files) {
88
180
  const relPath = relative(rootDir, filePath);
89
181
  const cached = this.fileCache.get(relPath);
90
182
  const source = readFileSync(filePath, "utf-8");
91
183
  const hash = createHash("md5").update(source).digest("hex");
92
184
  if (!cached || cached.hash !== hash) {
93
- hasChanges = true;
185
+ changedPaths.push(relPath);
94
186
  this.fileCache.set(relPath, {
95
187
  hash,
96
188
  source
97
189
  });
98
190
  }
99
191
  }
100
- if (hasChanges) {
192
+ if (changedPaths.length > 0) {
193
+ const fileEntries = this.buildFileEntriesFromCache(changedPaths);
101
194
  resetAnalysisPromise();
102
- const promise = this.runFullPipeline();
195
+ const promise = this.runIncrementalPipeline(fileEntries);
103
196
  setAnalysisPromise(promise);
104
197
  await promise;
105
198
  }
106
199
  }
107
200
  async runFullPipeline() {
201
+ const pipelineStart = this.now();
202
+ const bt = {};
203
+ try {
204
+ const { clearAnalysisCache } = __require("@animus-ui/extract");
205
+ clearAnalysisCache();
206
+ } catch {}
108
207
  const rootDir = this.rootDir;
109
208
  const resolvedSystemPath = resolve(rootDir, this.options.system);
209
+ let t = this.now();
110
210
  this.loadSystem(rootDir, resolvedSystemPath);
211
+ bt.systemLoad = this.elapsed(t);
212
+ t = this.now();
111
213
  const excludePatterns = this.options.exclude ?? [
112
214
  "node_modules",
113
215
  "dist",
@@ -117,6 +219,8 @@ var AnimusWebpackPlugin = class {
117
219
  ".animus"
118
220
  ];
119
221
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
222
+ bt.fileDiscovery = this.elapsed(t);
223
+ t = this.now();
120
224
  const fileEntries = [];
121
225
  for (const filePath of files) {
122
226
  const source = readFileSync(filePath, "utf-8");
@@ -132,6 +236,9 @@ var AnimusWebpackPlugin = class {
132
236
  hash
133
237
  });
134
238
  }
239
+ bt.fileRead = this.elapsed(t);
240
+ bt.fileCount = fileEntries.length;
241
+ t = this.now();
135
242
  const packageNames = extractSystemFilePackages(resolvedSystemPath);
136
243
  const packageMap = this.resolvePackagesByName(rootDir, packageNames);
137
244
  const excludePatternsForPkgs = [
@@ -173,6 +280,7 @@ var AnimusWebpackPlugin = class {
173
280
  } else collectedPkgDirs.push(dirname(absEntry));
174
281
  }
175
282
  this.externalPackageDirs = collectedPkgDirs;
283
+ bt.packageResolve = this.elapsed(t);
176
284
  const { analyzeProject } = __require("@animus-ui/extract");
177
285
  const animusDirPath = join(rootDir, ".animus");
178
286
  const emitterConfig = JSON.stringify({
@@ -180,10 +288,20 @@ var AnimusWebpackPlugin = class {
180
288
  css_module_id: ".animus/styles.css",
181
289
  system_props_module_id: join(animusDirPath, "system-props.js")
182
290
  });
183
- const manifestJson = analyzeProject(JSON.stringify(fileEntries), this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, JSON.stringify(packageMap), false, this.options.prefix || null, emitterConfig);
291
+ t = this.now();
292
+ const fileEntriesJson = JSON.stringify(fileEntries);
293
+ const packageMapJson = JSON.stringify(packageMap);
294
+ bt.jsonSerialize = this.elapsed(t);
295
+ t = this.now();
296
+ const manifestJson = analyzeProject(fileEntriesJson, this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, packageMapJson, false, emitterConfig, this.selectorAliasesJson, this.selectorOrderJson, this.globalStyleBlocksJson, this.pathAliasesJson);
297
+ bt.rustExtract = this.elapsed(t);
298
+ t = this.now();
184
299
  const manifest = JSON.parse(manifestJson);
300
+ bt.jsonParse = this.elapsed(t);
301
+ bt.analysis = (bt.jsonSerialize ?? 0) + (bt.rustExtract ?? 0) + (bt.jsonParse ?? 0);
302
+ if (manifest?.report) this.log(`Extracted ${manifest.report.components_extracted ?? "?"}/${manifest.report.components_total ?? "?"} components (${bt.analysis}ms)`);
303
+ this.globalCss = manifest?.sheets?.global || "";
185
304
  let componentCss = manifest?.css || "";
186
- if (this.systemResolveScript && componentCss.includes("__TRANSFORM__")) componentCss = this.resolveTransforms(rootDir, componentCss);
187
305
  componentCss = applyUnitFallback(componentCss);
188
306
  const fullCss = assembleStylesheet({
189
307
  layers: this.options.layers,
@@ -207,87 +325,33 @@ var AnimusWebpackPlugin = class {
207
325
  ...meta.transform_name ? { transformName: meta.transform_name } : {},
208
326
  ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
209
327
  };
210
- let transformsSource = "{}";
211
- if (this.systemResolveScript) try {
212
- const resolvedSystemPath = resolve(rootDir, this.options.system);
213
- const tmpOut = join(tmpdir(), `animus-transforms-${Date.now()}.json`);
214
- execSubprocess(`const m = require(${JSON.stringify(resolvedSystemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.toConfig();\nconst out = {};\nfor (const [name, fn] of Object.entries(cfg.transforms || {})) {\n out[name] = fn.toString();\n}\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify(out));\n`, rootDir);
215
- const fnStrings = JSON.parse(readFileSync(tmpOut, "utf-8"));
216
- try {
217
- __require("fs").unlinkSync(tmpOut);
218
- } catch {}
219
- transformsSource = `{\n${Object.entries(fnStrings).map(([name, src]) => ` ${JSON.stringify(name)}: ${src}`).join(",\n")}\n}`;
220
- } catch {}
328
+ const transformsSource = "{}";
221
329
  const animusDir = join(rootDir, ".animus");
222
330
  if (!existsSync(animusDir)) mkdirSync(animusDir, { recursive: true });
223
331
  writeFileSync(join(animusDir, "system-props.js"), `export const systemPropMap = ${systemPropMap};\nexport const systemPropGroups = ${this.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${transformsSource};\n`);
224
332
  setManifestJson(manifestJson);
333
+ bt.total = this.elapsed(pipelineStart);
334
+ this.logBuildTimings(bt, manifest?.timing);
225
335
  }
226
336
  loadSystem(rootDir, systemPath) {
227
- const tmpOut = join(tmpdir(), `animus-system-${Date.now()}.json`);
228
- execSubprocess(`const m = require(${JSON.stringify(systemPath)});\nconst exportNames = Object.keys(m);\nconst candidates = exportNames.filter(k => m[k] && typeof m[k].toConfig === 'function');\nif (candidates.length === 0) {\n throw new Error('[animus-extract] No SystemInstance found. Exports: [' + exportNames.join(', ') + ']. None have a .toConfig() method. A SystemInstance is created by: export const ds = createSystem().build()');\n}\nif (candidates.length > 1) {\n throw new Error('[animus-extract] Multiple SystemInstance candidates found: [' + candidates.join(', ') + ']. Specify which one to use.');\n}\nconst ds = m[candidates[0]];\nconst cfg = ds.toConfig();\nconst tokens = m.tokens || m.theme || null;\nconst serialized = tokens && typeof tokens.serialize === 'function' ? tokens.serialize() : null;\nconst globalStyleBlocks = {};\nfor (const [key, val] of Object.entries(m)) {\n if (val && typeof val === 'object' && val.__brand === 'GlobalStyleBlock') {\n globalStyleBlocks[key] = val.styles;\n }\n}\nconst transformSources = {};\nfor (const [name, fn] of Object.entries(cfg.transforms || {})) {\n transformSources[name] = fn.toString();\n}\nrequire('fs').writeFileSync(${JSON.stringify(tmpOut)}, JSON.stringify({\n propConfig: cfg.propConfig,\n groupRegistry: cfg.groupRegistry,\n serialized: serialized,\n transformNames: Object.keys(cfg.transforms || {}),\n transformSources: transformSources,\n globalStyleBlocks: Object.keys(globalStyleBlocks).length > 0 ? globalStyleBlocks : null\n}));\n`, rootDir);
229
- const result = readFileSync(tmpOut, "utf-8");
230
- try {
231
- __require("fs").unlinkSync(tmpOut);
232
- } catch {}
233
- const parsed = JSON.parse(result);
234
- this.configJson = parsed.propConfig;
235
- this.groupRegistryJson = parsed.groupRegistry;
236
- if (parsed.serialized) {
237
- this.themeJson = parsed.serialized.scalesJson;
238
- this.variableMapJson = parsed.serialized.variableMapJson;
239
- this.variableCss = parsed.serialized.variableCss;
240
- this.contextualVarsJson = parsed.serialized.contextualVarsJson;
241
- if (this.options.prefix) {
242
- const prefixed = applyPrefix(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
243
- this.variableMapJson = prefixed.variableMapJson;
244
- this.variableCss = prefixed.variableCss;
245
- if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
246
- if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
247
- }
248
- } else throw new Error("[animus-extract] Theme must be built with createTheme().build(). No .serialize() method found on tokens export.");
249
- if (parsed.globalStyleBlocks) {
250
- if (Object.keys(parsed.globalStyleBlocks).length > 0) try {
251
- const hasTransforms = parsed.transformNames && parsed.transformNames.length > 0;
252
- const flat = JSON.parse(this.themeJson);
253
- const propConfig = JSON.parse(this.configJson);
254
- const variableMap = {};
255
- for (const [tokenPath, value] of Object.entries(flat)) if (typeof value === "string" && value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
256
- const transforms = {};
257
- if (hasTransforms && parsed.transformSources) for (const [name, src] of Object.entries(parsed.transformSources)) try {
258
- transforms[name] = new Function(`return (${src})`)();
259
- } catch {}
260
- const gsResult = resolveGlobalStyles(parsed.globalStyleBlocks, propConfig, flat, variableMap, transforms);
261
- const parts = Object.values(gsResult).filter(Boolean);
262
- if (parts.length > 0) this.globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
263
- } catch (e) {
264
- const msg = e instanceof Error ? e.message : String(e);
265
- if (this.options.strict) throw new Error(`[animus-extract] Global styles resolution failed: ${msg}`);
266
- console.warn("[animus-extract] Global styles resolution failed:", msg);
267
- }
268
- }
269
- if (parsed.transformNames && parsed.transformNames.length > 0) {
270
- const tmpResolve = join(tmpdir(), `animus-transforms-resolve-${Date.now()}.js`);
271
- writeFileSync(tmpResolve, `const m = require(${JSON.stringify(systemPath)});\nconst ds = m.ds || m.default || m.system;\nconst cfg = ds.toConfig();\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`);
272
- this.systemResolveScript = tmpResolve;
337
+ const { loadSystemModule } = __require("@animus-ui/extract");
338
+ const config = loadSystemModule(systemPath, rootDir);
339
+ this.configJson = config.propConfig;
340
+ this.groupRegistryJson = config.groupRegistry;
341
+ this.themeJson = config.scalesJson;
342
+ this.variableMapJson = config.variableMapJson;
343
+ this.variableCss = config.variableCss;
344
+ this.contextualVarsJson = config.contextualVarsJson;
345
+ this.selectorAliasesJson = config.selectorAliases || null;
346
+ this.selectorOrderJson = config.selectorOrder || null;
347
+ if (this.options.prefix) {
348
+ const prefixed = applyPrefix(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
349
+ this.variableMapJson = prefixed.variableMapJson;
350
+ this.variableCss = prefixed.variableCss;
351
+ if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
352
+ if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
273
353
  }
274
- }
275
- resolveTransforms(rootDir, css) {
276
- const { execSync } = __require("child_process");
277
- const ts = Date.now();
278
- const tmpIn = join(tmpdir(), `animus-css-${ts}.css`);
279
- const tmpOut = join(tmpdir(), `animus-css-${ts}.out.css`);
280
- writeFileSync(tmpIn, css);
281
- execSync(`${detectRuntime()} run "${this.systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
282
- cwd: rootDir,
283
- encoding: "utf-8"
284
- });
285
- const resolved = readFileSync(tmpOut, "utf-8");
286
- try {
287
- __require("fs").unlinkSync(tmpIn);
288
- __require("fs").unlinkSync(tmpOut);
289
- } catch {}
290
- return resolved;
354
+ this.globalStyleBlocksJson = config.globalStyleBlocks || null;
291
355
  }
292
356
  discoverFiles(dir, rootDir, excludePatterns) {
293
357
  const results = [];
@@ -363,6 +427,64 @@ var AnimusWebpackPlugin = class {
363
427
  clearAnalysisCache();
364
428
  } catch {}
365
429
  }
430
+ /**
431
+ * Build cache-aware file entries: full source for changed files,
432
+ * empty source + hash for unchanged (Rust cache-hit path skips these).
433
+ */
434
+ buildFileEntriesFromCache(changedPaths) {
435
+ const changedSet = new Set(changedPaths);
436
+ const entries = [];
437
+ for (const [path, { hash, source }] of this.fileCache) entries.push({
438
+ path,
439
+ source: changedSet.has(path) ? source : "",
440
+ hash
441
+ });
442
+ return entries;
443
+ }
444
+ /**
445
+ * Run incremental pipeline with cache-aware file entries.
446
+ * Reuses system config from the last full pipeline run.
447
+ */
448
+ async runIncrementalPipeline(fileEntries) {
449
+ const rootDir = this.rootDir;
450
+ const bt = {};
451
+ const pipelineStart = this.now();
452
+ const { analyzeProject } = __require("@animus-ui/extract");
453
+ const animusDirPath = join(rootDir, ".animus");
454
+ const emitterConfig = JSON.stringify({
455
+ runtime_import: "@animus-ui/system/runtime",
456
+ css_module_id: join(animusDirPath, "system-props.js")
457
+ });
458
+ const packageMapJson = JSON.stringify(Object.fromEntries(Array.from(this.externalSourceEntries).map(([spec, entry]) => [spec, relative(rootDir, entry)])));
459
+ let t = this.now();
460
+ const fileEntriesJson = JSON.stringify(fileEntries);
461
+ bt.jsonSerialize = this.elapsed(t);
462
+ t = this.now();
463
+ const manifestJson = analyzeProject(fileEntriesJson, this.themeJson, this.variableMapJson, this.contextualVarsJson || null, this.configJson, this.groupRegistryJson, packageMapJson, false, emitterConfig, this.selectorAliasesJson, this.selectorOrderJson, this.globalStyleBlocksJson, this.pathAliasesJson);
464
+ bt.rustExtract = this.elapsed(t);
465
+ t = this.now();
466
+ const manifest = JSON.parse(manifestJson);
467
+ bt.jsonParse = this.elapsed(t);
468
+ this.globalCss = manifest?.sheets?.global || "";
469
+ let componentCss = manifest?.css || "";
470
+ componentCss = applyUnitFallback(componentCss);
471
+ const fullCss = assembleStylesheet({
472
+ layers: this.options.layers,
473
+ variableCss: this.variableCss,
474
+ globalCss: this.globalCss,
475
+ componentCss
476
+ });
477
+ const cssHash = createHash("md5").update(fullCss).digest("hex");
478
+ if (cssHash !== this.lastCssHash) {
479
+ const animusDir = join(rootDir, ".animus");
480
+ if (!existsSync(animusDir)) mkdirSync(animusDir, { recursive: true });
481
+ writeFileSync(join(animusDir, "styles.css"), fullCss);
482
+ this.lastCssHash = cssHash;
483
+ }
484
+ setManifestJson(manifestJson);
485
+ bt.total = this.elapsed(pipelineStart);
486
+ this.logBuildTimings(bt, manifest?.timing);
487
+ }
366
488
  /** Expose file cache for HMR change detection */
367
489
  getFileCache() {
368
490
  return this.fileCache;
@@ -429,6 +551,7 @@ function withAnimus(options) {
429
551
  compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
430
552
  nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
431
553
  if (resolveData.request === "virtual:animus/system-props") resolveData.request = systemPropsPath;
554
+ if (resolveData.request === "virtual:animus/styles.css") resolveData.request = join(rootDir, ".animus", "styles.css");
432
555
  const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
433
556
  if (srcEntry) resolveData.request = srcEntry;
434
557
  });
package/dist/loader.cjs CHANGED
@@ -3,10 +3,12 @@ let path = require("path");
3
3
  //#region src/loader.ts
4
4
  /**
5
5
  * Regex matching CSS import lines injected by the Rust emitter.
6
- * Catches both the alias form (`import '.animus/styles.css'`) and
7
- * relative forms (`import '../.animus/styles.css'`).
6
+ * Catches both the alias form (`import '.animus/styles.css'`),
7
+ * relative forms (`import '../.animus/styles.css'`), and the Vite
8
+ * virtual module form (`import 'virtual:animus/styles.css'`) which
9
+ * appears in pre-built external packages compiled with the Vite plugin.
8
10
  */
9
- const CSS_IMPORT_RE = /import\s+['"][^'"]*\.animus\/styles\.css['"];\n?/g;
11
+ const CSS_IMPORT_RE = /import\s+['"](?:[^'"]*\.animus\/styles\.css|virtual:animus\/styles\.css)['"];\n?/g;
10
12
  /**
11
13
  * Root entry file patterns. CSS is imported ONLY here to prevent
12
14
  * per-chunk duplication in Next.js builds.
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxC,CAAC;AAoBF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CAwCR"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxC,CAAC;AAuBF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CAwCR"}
package/dist/loader.mjs CHANGED
@@ -3,10 +3,12 @@ import { relative } from "path";
3
3
  //#region src/loader.ts
4
4
  /**
5
5
  * Regex matching CSS import lines injected by the Rust emitter.
6
- * Catches both the alias form (`import '.animus/styles.css'`) and
7
- * relative forms (`import '../.animus/styles.css'`).
6
+ * Catches both the alias form (`import '.animus/styles.css'`),
7
+ * relative forms (`import '../.animus/styles.css'`), and the Vite
8
+ * virtual module form (`import 'virtual:animus/styles.css'`) which
9
+ * appears in pre-built external packages compiled with the Vite plugin.
8
10
  */
9
- const CSS_IMPORT_RE = /import\s+['"][^'"]*\.animus\/styles\.css['"];\n?/g;
11
+ const CSS_IMPORT_RE = /import\s+['"](?:[^'"]*\.animus\/styles\.css|virtual:animus\/styles\.css)['"];\n?/g;
10
12
  /**
11
13
  * Root entry file patterns. CSS is imported ONLY here to prevent
12
14
  * per-chunk duplication in Next.js builds.
package/dist/plugin.d.ts CHANGED
@@ -9,6 +9,11 @@ type Compiler = {
9
9
  };
10
10
  };
11
11
  context: string;
12
+ options?: {
13
+ resolve?: {
14
+ alias?: Record<string, string | string[] | false>;
15
+ };
16
+ };
12
17
  };
13
18
  export declare class AnimusWebpackPlugin {
14
19
  private options;
@@ -19,25 +24,44 @@ export declare class AnimusWebpackPlugin {
19
24
  private variableMapJson;
20
25
  private variableCss;
21
26
  private contextualVarsJson;
27
+ private selectorAliasesJson;
28
+ private selectorOrderJson;
22
29
  private globalCss;
23
- private systemResolveScript;
30
+ private globalStyleBlocksJson;
31
+ private pathAliasesJson;
24
32
  private fileCache;
25
33
  private lastCssHash;
26
34
  private externalPackageDirs;
27
35
  private externalSourceEntries;
28
36
  constructor(options: AnimusNextOptions);
37
+ private get verbose();
38
+ private log;
39
+ private now;
40
+ private elapsed;
41
+ private logBuildTimings;
29
42
  private initialized;
43
+ /** Extract path aliases from webpack's resolve.alias config. */
44
+ private extractAliases;
30
45
  apply(compiler: Compiler): void;
31
46
  private handleWatchUpdate;
32
47
  private runFullPipeline;
33
48
  private loadSystem;
34
- private resolveTransforms;
35
49
  private discoverFiles;
36
50
  private resolvePackagesByName;
37
51
  /**
38
52
  * Reset analysis state for HMR geological reset.
39
53
  */
40
54
  resetForHmr(): void;
55
+ /**
56
+ * Build cache-aware file entries: full source for changed files,
57
+ * empty source + hash for unchanged (Rust cache-hit path skips these).
58
+ */
59
+ private buildFileEntriesFromCache;
60
+ /**
61
+ * Run incremental pipeline with cache-aware file entries.
62
+ * Reuses system config from the last full pipeline run.
63
+ */
64
+ private runIncrementalPipeline;
41
65
  /** Expose file cache for HMR change detection */
42
66
  getFileCache(): Map<string, {
43
67
  hash: string;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,KAAK,QAAQ,GAAG;IACd,KAAK,EAAE;QACL,GAAG,EAAE;YACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;SACxE,CAAC;QACF,QAAQ,EAAE;YACR,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;SACxE,CAAC;KACH,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,OAAO,CAAuB;IAGtC,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,mBAAmB,CAAuB;IAGlD,OAAO,CAAC,SAAS,CAAuD;IACxE,OAAO,CAAC,WAAW,CAAuB;IAG1C,OAAO,CAAC,mBAAmB,CAAgB;IAG3C,OAAO,CAAC,qBAAqB,CAA6B;gBAE9C,OAAO,EAAE,iBAAiB;IAItC,OAAO,CAAC,WAAW,CAAS;IAE5B,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;YA2CjB,iBAAiB;YAwDjB,eAAe;IA8M7B,OAAO,CAAC,UAAU;IAoKlB,OAAO,CAAC,iBAAiB;IAqBzB,OAAO,CAAC,aAAa;IAyCrB,OAAO,CAAC,qBAAqB;IAmD7B;;OAEG;IACH,WAAW,IAAI,IAAI;IASnB,iDAAiD;IACjD,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAI7D,oCAAoC;IACpC,UAAU,IAAI,iBAAiB;IAI/B,0EAA0E;IAC1E,sBAAsB,IAAI,MAAM,EAAE;IAIlC,uEAAuE;IACvE,wBAAwB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAGhD"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,KAAK,QAAQ,GAAG;IACd,KAAK,EAAE;QACL,GAAG,EAAE;YACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;SACxE,CAAC;QACF,QAAQ,EAAE;YACR,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;SACxE,CAAC;KACH,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE;YACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;SACnD,CAAC;KACH,CAAC;CACH,CAAC;AAIF,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,OAAO,CAAuB;IAGtC,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,qBAAqB,CAAuB;IACpD,OAAO,CAAC,eAAe,CAAuB;IAG9C,OAAO,CAAC,SAAS,CAAuD;IACxE,OAAO,CAAC,WAAW,CAAuB;IAG1C,OAAO,CAAC,mBAAmB,CAAgB;IAG3C,OAAO,CAAC,qBAAqB,CAA6B;gBAE9C,OAAO,EAAE,iBAAiB;IAItC,OAAO,KAAK,OAAO,GAMlB;IAED,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,GAAG;IAGX,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,eAAe;IAyEvB,OAAO,CAAC,WAAW,CAAS;IAE5B,gEAAgE;IAChE,OAAO,CAAC,cAAc;IAyCtB,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;YA6CjB,iBAAiB;YA2DjB,eAAe;IA0O7B,OAAO,CAAC,UAAU;IAgClB,OAAO,CAAC,aAAa;IAyCrB,OAAO,CAAC,qBAAqB;IAmD7B;;OAEG;IACH,WAAW,IAAI,IAAI;IASnB;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAejC;;;OAGG;YACW,sBAAsB;IA+EpC,iDAAiD;IACjD,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAI7D,oCAAoC;IACpC,UAAU,IAAI,iBAAiB;IAI/B,0EAA0E;IAC1E,sBAAsB,IAAI,MAAM,EAAE;IAIlC,uEAAuE;IACvE,wBAAwB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAGhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"with-animus.d.ts","sourceRoot":"","sources":["../src/with-animus.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAKjD,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,KAAK,CAAC;YACZ,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;YAC5C,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;YAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,GAAG,CAAC,EAAE,KAAK,CAAC;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;aAAE,CAAC,CAAC;SACpE,CAAC,CAAC;KACJ,CAAC;CACH,CAAC;AAEF,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC1C,OAAO,CAAC,EAAE,CACR,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,aAAa,CAAC;CACpB,CAAC;AAIF;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,GACzB,CAAC,UAAU,EAAE,UAAU,KAAK,UAAU,CA4JxC"}
1
+ {"version":3,"file":"with-animus.d.ts","sourceRoot":"","sources":["../src/with-animus.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAKjD,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,KAAK,CAAC;YACZ,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;YAC5C,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;YAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,GAAG,CAAC,EAAE,KAAK,CAAC;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;aAAE,CAAC,CAAC;SACpE,CAAC,CAAC;KACJ,CAAC;CACH,CAAC;AAEF,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC1C,OAAO,CAAC,EAAE,CACR,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,aAAa,CAAC;CACpB,CAAC;AAIF;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,GACzB,CAAC,UAAU,EAAE,UAAU,KAAK,UAAU,CAuKxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animus-ui/next-plugin",
3
- "version": "0.1.0-next.48",
3
+ "version": "0.1.0-next.58",
4
4
  "description": "Animus static CSS extraction for Next.js (webpack)",
5
5
  "author": "codecaaron <airrobb@gmail.com>",
6
6
  "license": "MIT",
@@ -53,6 +53,6 @@
53
53
  "webpack": ">=5.0.0"
54
54
  },
55
55
  "dependencies": {
56
- "@animus-ui/extract": "0.1.0-next.48"
56
+ "@animus-ui/extract": "0.1.0-next.58"
57
57
  }
58
58
  }