@animus-ui/next-plugin 0.1.0-next.49 → 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,35 +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 = {};
109
204
  try {
110
205
  const { clearAnalysisCache } = require("@animus-ui/extract");
111
206
  clearAnalysisCache();
112
207
  } catch {}
113
208
  const rootDir = this.rootDir;
114
209
  const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
210
+ let t = this.now();
115
211
  this.loadSystem(rootDir, resolvedSystemPath);
212
+ bt.systemLoad = this.elapsed(t);
213
+ t = this.now();
116
214
  const excludePatterns = this.options.exclude ?? [
117
215
  "node_modules",
118
216
  "dist",
@@ -122,6 +220,8 @@ var AnimusWebpackPlugin = class {
122
220
  ".animus"
123
221
  ];
124
222
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
223
+ bt.fileDiscovery = this.elapsed(t);
224
+ t = this.now();
125
225
  const fileEntries = [];
126
226
  for (const filePath of files) {
127
227
  const source = (0, fs.readFileSync)(filePath, "utf-8");
@@ -137,6 +237,9 @@ var AnimusWebpackPlugin = class {
137
237
  hash
138
238
  });
139
239
  }
240
+ bt.fileRead = this.elapsed(t);
241
+ bt.fileCount = fileEntries.length;
242
+ t = this.now();
140
243
  const packageNames = (0, _animus_ui_extract_pipeline.extractSystemFilePackages)(resolvedSystemPath);
141
244
  const packageMap = this.resolvePackagesByName(rootDir, packageNames);
142
245
  const excludePatternsForPkgs = [
@@ -178,6 +281,7 @@ var AnimusWebpackPlugin = class {
178
281
  } else collectedPkgDirs.push((0, path.dirname)(absEntry));
179
282
  }
180
283
  this.externalPackageDirs = collectedPkgDirs;
284
+ bt.packageResolve = this.elapsed(t);
181
285
  const { analyzeProject } = require("@animus-ui/extract");
182
286
  const animusDirPath = (0, path.join)(rootDir, ".animus");
183
287
  const emitterConfig = JSON.stringify({
@@ -185,10 +289,20 @@ var AnimusWebpackPlugin = class {
185
289
  css_module_id: ".animus/styles.css",
186
290
  system_props_module_id: (0, path.join)(animusDirPath, "system-props.js")
187
291
  });
188
- 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();
189
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 || "";
190
305
  let componentCss = manifest?.css || "";
191
- if (this.systemResolveScript && componentCss.includes("__TRANSFORM__")) componentCss = this.resolveTransforms(rootDir, componentCss);
192
306
  componentCss = (0, _animus_ui_extract_pipeline.applyUnitFallback)(componentCss);
193
307
  const fullCss = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
194
308
  layers: this.options.layers,
@@ -212,87 +326,33 @@ var AnimusWebpackPlugin = class {
212
326
  ...meta.transform_name ? { transformName: meta.transform_name } : {},
213
327
  ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
214
328
  };
215
- let transformsSource = "{}";
216
- if (this.systemResolveScript) try {
217
- const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
218
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-transforms-${Date.now()}.json`);
219
- (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);
220
- const fnStrings = JSON.parse((0, fs.readFileSync)(tmpOut, "utf-8"));
221
- try {
222
- require("fs").unlinkSync(tmpOut);
223
- } catch {}
224
- transformsSource = `{\n${Object.entries(fnStrings).map(([name, src]) => ` ${JSON.stringify(name)}: ${src}`).join(",\n")}\n}`;
225
- } catch {}
329
+ const transformsSource = "{}";
226
330
  const animusDir = (0, path.join)(rootDir, ".animus");
227
331
  if (!(0, fs.existsSync)(animusDir)) (0, fs.mkdirSync)(animusDir, { recursive: true });
228
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`);
229
333
  require_singleton.setManifestJson(manifestJson);
334
+ bt.total = this.elapsed(pipelineStart);
335
+ this.logBuildTimings(bt, manifest?.timing);
230
336
  }
231
337
  loadSystem(rootDir, systemPath) {
232
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-system-${Date.now()}.json`);
233
- (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);
234
- const result = (0, fs.readFileSync)(tmpOut, "utf-8");
235
- try {
236
- require("fs").unlinkSync(tmpOut);
237
- } catch {}
238
- const parsed = JSON.parse(result);
239
- this.configJson = parsed.propConfig;
240
- this.groupRegistryJson = parsed.groupRegistry;
241
- if (parsed.serialized) {
242
- this.themeJson = parsed.serialized.scalesJson;
243
- this.variableMapJson = parsed.serialized.variableMapJson;
244
- this.variableCss = parsed.serialized.variableCss;
245
- this.contextualVarsJson = parsed.serialized.contextualVarsJson;
246
- if (this.options.prefix) {
247
- const prefixed = (0, _animus_ui_extract_pipeline.applyPrefix)(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
248
- this.variableMapJson = prefixed.variableMapJson;
249
- this.variableCss = prefixed.variableCss;
250
- if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
251
- if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
252
- }
253
- } else throw new Error("[animus-extract] Theme must be built with createTheme().build(). No .serialize() method found on tokens export.");
254
- if (parsed.globalStyleBlocks) {
255
- if (Object.keys(parsed.globalStyleBlocks).length > 0) try {
256
- const hasTransforms = parsed.transformNames && parsed.transformNames.length > 0;
257
- const flat = JSON.parse(this.themeJson);
258
- const propConfig = JSON.parse(this.configJson);
259
- const variableMap = {};
260
- for (const [tokenPath, value] of Object.entries(flat)) if (typeof value === "string" && value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
261
- const transforms = {};
262
- if (hasTransforms && parsed.transformSources) for (const [name, src] of Object.entries(parsed.transformSources)) try {
263
- transforms[name] = new Function(`return (${src})`)();
264
- } catch {}
265
- const gsResult = (0, _animus_ui_extract_pipeline.resolveGlobalStyles)(parsed.globalStyleBlocks, propConfig, flat, variableMap, transforms);
266
- const parts = Object.values(gsResult).filter(Boolean);
267
- if (parts.length > 0) this.globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
268
- } catch (e) {
269
- const msg = e instanceof Error ? e.message : String(e);
270
- if (this.options.strict) throw new Error(`[animus-extract] Global styles resolution failed: ${msg}`);
271
- console.warn("[animus-extract] Global styles resolution failed:", msg);
272
- }
273
- }
274
- if (parsed.transformNames && parsed.transformNames.length > 0) {
275
- const tmpResolve = (0, path.join)((0, os.tmpdir)(), `animus-transforms-resolve-${Date.now()}.js`);
276
- (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`);
277
- 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;
278
354
  }
279
- }
280
- resolveTransforms(rootDir, css) {
281
- const { execSync } = require("child_process");
282
- const ts = Date.now();
283
- const tmpIn = (0, path.join)((0, os.tmpdir)(), `animus-css-${ts}.css`);
284
- const tmpOut = (0, path.join)((0, os.tmpdir)(), `animus-css-${ts}.out.css`);
285
- (0, fs.writeFileSync)(tmpIn, css);
286
- execSync(`${(0, _animus_ui_extract_pipeline.detectRuntime)()} run "${this.systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
287
- cwd: rootDir,
288
- encoding: "utf-8"
289
- });
290
- const resolved = (0, fs.readFileSync)(tmpOut, "utf-8");
291
- try {
292
- require("fs").unlinkSync(tmpIn);
293
- require("fs").unlinkSync(tmpOut);
294
- } catch {}
295
- return resolved;
355
+ this.globalStyleBlocksJson = config.globalStyleBlocks || null;
296
356
  }
297
357
  discoverFiles(dir, rootDir, excludePatterns) {
298
358
  const results = [];
@@ -368,6 +428,64 @@ var AnimusWebpackPlugin = class {
368
428
  clearAnalysisCache();
369
429
  } catch {}
370
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
+ }
371
489
  /** Expose file cache for HMR change detection */
372
490
  getFileCache() {
373
491
  return this.fileCache;
@@ -434,6 +552,7 @@ function withAnimus(options) {
434
552
  compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
435
553
  nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
436
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");
437
556
  const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
438
557
  if (srcEntry) resolveData.request = srcEntry;
439
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,35 +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 = {};
108
203
  try {
109
204
  const { clearAnalysisCache } = __require("@animus-ui/extract");
110
205
  clearAnalysisCache();
111
206
  } catch {}
112
207
  const rootDir = this.rootDir;
113
208
  const resolvedSystemPath = resolve(rootDir, this.options.system);
209
+ let t = this.now();
114
210
  this.loadSystem(rootDir, resolvedSystemPath);
211
+ bt.systemLoad = this.elapsed(t);
212
+ t = this.now();
115
213
  const excludePatterns = this.options.exclude ?? [
116
214
  "node_modules",
117
215
  "dist",
@@ -121,6 +219,8 @@ var AnimusWebpackPlugin = class {
121
219
  ".animus"
122
220
  ];
123
221
  const files = this.discoverFiles(rootDir, rootDir, excludePatterns);
222
+ bt.fileDiscovery = this.elapsed(t);
223
+ t = this.now();
124
224
  const fileEntries = [];
125
225
  for (const filePath of files) {
126
226
  const source = readFileSync(filePath, "utf-8");
@@ -136,6 +236,9 @@ var AnimusWebpackPlugin = class {
136
236
  hash
137
237
  });
138
238
  }
239
+ bt.fileRead = this.elapsed(t);
240
+ bt.fileCount = fileEntries.length;
241
+ t = this.now();
139
242
  const packageNames = extractSystemFilePackages(resolvedSystemPath);
140
243
  const packageMap = this.resolvePackagesByName(rootDir, packageNames);
141
244
  const excludePatternsForPkgs = [
@@ -177,6 +280,7 @@ var AnimusWebpackPlugin = class {
177
280
  } else collectedPkgDirs.push(dirname(absEntry));
178
281
  }
179
282
  this.externalPackageDirs = collectedPkgDirs;
283
+ bt.packageResolve = this.elapsed(t);
180
284
  const { analyzeProject } = __require("@animus-ui/extract");
181
285
  const animusDirPath = join(rootDir, ".animus");
182
286
  const emitterConfig = JSON.stringify({
@@ -184,10 +288,20 @@ var AnimusWebpackPlugin = class {
184
288
  css_module_id: ".animus/styles.css",
185
289
  system_props_module_id: join(animusDirPath, "system-props.js")
186
290
  });
187
- 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();
188
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 || "";
189
304
  let componentCss = manifest?.css || "";
190
- if (this.systemResolveScript && componentCss.includes("__TRANSFORM__")) componentCss = this.resolveTransforms(rootDir, componentCss);
191
305
  componentCss = applyUnitFallback(componentCss);
192
306
  const fullCss = assembleStylesheet({
193
307
  layers: this.options.layers,
@@ -211,87 +325,33 @@ var AnimusWebpackPlugin = class {
211
325
  ...meta.transform_name ? { transformName: meta.transform_name } : {},
212
326
  ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
213
327
  };
214
- let transformsSource = "{}";
215
- if (this.systemResolveScript) try {
216
- const resolvedSystemPath = resolve(rootDir, this.options.system);
217
- const tmpOut = join(tmpdir(), `animus-transforms-${Date.now()}.json`);
218
- 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);
219
- const fnStrings = JSON.parse(readFileSync(tmpOut, "utf-8"));
220
- try {
221
- __require("fs").unlinkSync(tmpOut);
222
- } catch {}
223
- transformsSource = `{\n${Object.entries(fnStrings).map(([name, src]) => ` ${JSON.stringify(name)}: ${src}`).join(",\n")}\n}`;
224
- } catch {}
328
+ const transformsSource = "{}";
225
329
  const animusDir = join(rootDir, ".animus");
226
330
  if (!existsSync(animusDir)) mkdirSync(animusDir, { recursive: true });
227
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`);
228
332
  setManifestJson(manifestJson);
333
+ bt.total = this.elapsed(pipelineStart);
334
+ this.logBuildTimings(bt, manifest?.timing);
229
335
  }
230
336
  loadSystem(rootDir, systemPath) {
231
- const tmpOut = join(tmpdir(), `animus-system-${Date.now()}.json`);
232
- 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);
233
- const result = readFileSync(tmpOut, "utf-8");
234
- try {
235
- __require("fs").unlinkSync(tmpOut);
236
- } catch {}
237
- const parsed = JSON.parse(result);
238
- this.configJson = parsed.propConfig;
239
- this.groupRegistryJson = parsed.groupRegistry;
240
- if (parsed.serialized) {
241
- this.themeJson = parsed.serialized.scalesJson;
242
- this.variableMapJson = parsed.serialized.variableMapJson;
243
- this.variableCss = parsed.serialized.variableCss;
244
- this.contextualVarsJson = parsed.serialized.contextualVarsJson;
245
- if (this.options.prefix) {
246
- const prefixed = applyPrefix(this.options.prefix, this.variableMapJson, this.variableCss, this.themeJson, this.contextualVarsJson || void 0);
247
- this.variableMapJson = prefixed.variableMapJson;
248
- this.variableCss = prefixed.variableCss;
249
- if (prefixed.themeJson) this.themeJson = prefixed.themeJson;
250
- if (prefixed.contextualVarsJson) this.contextualVarsJson = prefixed.contextualVarsJson;
251
- }
252
- } else throw new Error("[animus-extract] Theme must be built with createTheme().build(). No .serialize() method found on tokens export.");
253
- if (parsed.globalStyleBlocks) {
254
- if (Object.keys(parsed.globalStyleBlocks).length > 0) try {
255
- const hasTransforms = parsed.transformNames && parsed.transformNames.length > 0;
256
- const flat = JSON.parse(this.themeJson);
257
- const propConfig = JSON.parse(this.configJson);
258
- const variableMap = {};
259
- for (const [tokenPath, value] of Object.entries(flat)) if (typeof value === "string" && value.startsWith("var(") && value.endsWith(")")) variableMap[tokenPath] = value.slice(4, -1);
260
- const transforms = {};
261
- if (hasTransforms && parsed.transformSources) for (const [name, src] of Object.entries(parsed.transformSources)) try {
262
- transforms[name] = new Function(`return (${src})`)();
263
- } catch {}
264
- const gsResult = resolveGlobalStyles(parsed.globalStyleBlocks, propConfig, flat, variableMap, transforms);
265
- const parts = Object.values(gsResult).filter(Boolean);
266
- if (parts.length > 0) this.globalCss = `@layer global {\n${parts.join("\n\n")}\n}`;
267
- } catch (e) {
268
- const msg = e instanceof Error ? e.message : String(e);
269
- if (this.options.strict) throw new Error(`[animus-extract] Global styles resolution failed: ${msg}`);
270
- console.warn("[animus-extract] Global styles resolution failed:", msg);
271
- }
272
- }
273
- if (parsed.transformNames && parsed.transformNames.length > 0) {
274
- const tmpResolve = join(tmpdir(), `animus-transforms-resolve-${Date.now()}.js`);
275
- 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`);
276
- 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;
277
353
  }
278
- }
279
- resolveTransforms(rootDir, css) {
280
- const { execSync } = __require("child_process");
281
- const ts = Date.now();
282
- const tmpIn = join(tmpdir(), `animus-css-${ts}.css`);
283
- const tmpOut = join(tmpdir(), `animus-css-${ts}.out.css`);
284
- writeFileSync(tmpIn, css);
285
- execSync(`${detectRuntime()} run "${this.systemResolveScript}" "${tmpIn}" "${tmpOut}"`, {
286
- cwd: rootDir,
287
- encoding: "utf-8"
288
- });
289
- const resolved = readFileSync(tmpOut, "utf-8");
290
- try {
291
- __require("fs").unlinkSync(tmpIn);
292
- __require("fs").unlinkSync(tmpOut);
293
- } catch {}
294
- return resolved;
354
+ this.globalStyleBlocksJson = config.globalStyleBlocks || null;
295
355
  }
296
356
  discoverFiles(dir, rootDir, excludePatterns) {
297
357
  const results = [];
@@ -367,6 +427,64 @@ var AnimusWebpackPlugin = class {
367
427
  clearAnalysisCache();
368
428
  } catch {}
369
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
+ }
370
488
  /** Expose file cache for HMR change detection */
371
489
  getFileCache() {
372
490
  return this.fileCache;
@@ -433,6 +551,7 @@ function withAnimus(options) {
433
551
  compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
434
552
  nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
435
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");
436
555
  const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
437
556
  if (srcEntry) resolveData.request = srcEntry;
438
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;IAuN7B,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.49",
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.49"
56
+ "@animus-ui/extract": "0.1.0-next.58"
57
57
  }
58
58
  }