@menapo/vicss 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.js ADDED
@@ -0,0 +1,2264 @@
1
+ import { $ as isPaletteAtom, Ct as isArbitrary, Dt as mergeIdOf, G as minLengthOf, H as createNamer, J as placeholdersIn, K as minLengthWarning, U as hasPlaceholders, Y as renameClasses, _t as VicssError, bt as compareStrings, d as printSheet, k as layerStatement, l as printAtoms, n as chainSelectors, q as placeholder, s as outerLayers, t as baseTokens, u as printBase } from "./print-B_mn7wBu.js";
2
+ import { S as originalLines, _ as isSfc, d as FRAMEWORKS, f as SFC_SCRIPT_ID, h as extractScripts, i as poolSize, l as resolveFile, m as detectFramework, o as compile, p as configuredJsxSource, r as configShape, s as createParseCache, t as CompilePool, u as resolvesAlike, x as SourceEdits } from "./pool-F3qb30Fy.js";
3
+ import { C as createLocator, u as baseAtoms } from "./theme-zBcr5DHi.js";
4
+ import { C as CompileCache, D as generateTypes, E as readHash, O as createManifest, S as report, T as hashCode, _ as critical, a as ScanConflict, b as hashesJson, c as scan, d as libraryOf, f as loadLibraries, g as SheetCache, h as withLibraries, i as writeIfChanged, k as codeFrame, l as escapeRegExp, m as renameCustomProperties, n as snapshotRoot, o as estimateStyled, p as ownPackageName, r as typesFile, s as listFiles, t as snapshotFile, u as findLibraries, v as criticalPage, w as compiledOf, y as criticalSettings } from "./types-file-D6Iu4kzv.js";
5
+ import { r as snapshotText } from "./snapshot-DAJ8FLU4.js";
6
+ import { n as loadSystem, t as CONFIG_FILES } from "./config-loader-1kWS0cRU.js";
7
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
8
+ import { readFile } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { gzipSync } from "node:zlib";
11
+ import { createIdResolver, isFileLoadingAllowed, normalizePath, parseSync } from "vite";
12
+ //#region src/core/literals.ts
13
+ /**
14
+ * Literal declarations (the plugin's `api.literals()`, email PLAN D4): each
15
+ * atom with its tokens resolved to the values they stand for, for a renderer
16
+ * that cannot read custom properties. Mail clients drop `var()` and cascade
17
+ * layers, so `@menapo/email` inlines these instead of linking the sheet.
18
+ */
19
+ const VAR = /var\((--[\w-]+)\)/g;
20
+ const SCHEMES = /* @__PURE__ */ new Set(["dark", "light"]);
21
+ function tokenIndex(system) {
22
+ const index = /* @__PURE__ */ new Map();
23
+ for (const table of system.tokens.values()) for (const token of table.values()) index.set(token.cssVar, token);
24
+ return index;
25
+ }
26
+ /** `value` with every token's `var()` replaced by its value in `mode` (its base value without one). */
27
+ function resolve(value, index, mode) {
28
+ return value.replace(VAR, (match, name) => {
29
+ const token = index.get(name);
30
+ if (!token) return match;
31
+ return resolve((mode && token.modes?.[mode]) ?? token.value, index, mode);
32
+ });
33
+ }
34
+ /**
35
+ * Every class of `atoms` and of the flattened bases in `rules`, by identity,
36
+ * with its declarations in literal form. A flattened base is one class with
37
+ * many declarations; an atom is one with one.
38
+ */
39
+ function literalsOf(atoms, rules, system) {
40
+ const index = tokenIndex(system);
41
+ const modes = [...system.modes].filter((mode) => system.conditions.get(mode)?.scope);
42
+ const literal = (atom) => {
43
+ const value = resolve(atom.value, index);
44
+ const switched = {};
45
+ let any = false;
46
+ for (const mode of modes) {
47
+ const there = resolve(atom.value, index, mode);
48
+ if (there !== value) {
49
+ switched[mode] = there;
50
+ any = true;
51
+ }
52
+ }
53
+ const conditions = atom.chain.filter((name) => !SCHEMES.has(name));
54
+ return {
55
+ property: atom.property,
56
+ value,
57
+ tier: atom.tier,
58
+ chain: atom.chain,
59
+ rules: chainSelectors(conditions, "&", system),
60
+ ...any && { modes: switched }
61
+ };
62
+ };
63
+ const out = /* @__PURE__ */ new Map();
64
+ for (const atom of atoms) if (!out.has(atom.className)) out.set(atom.className, [literal(atom)]);
65
+ for (const rule of rules) if (rule.kind === "base" && rule.atoms) out.set(rule.id, rule.atoms.map(literal));
66
+ return out;
67
+ }
68
+ const STRINGS = /(["'`])((?:\\.|(?!\1)[^\\\n])*)\1/g;
69
+ /** The classes of `known` that string literals in `code` name (dev code writes classes as they are). */
70
+ function classesIn(code, known) {
71
+ const found = /* @__PURE__ */ new Set();
72
+ for (const match of code.matchAll(STRINGS)) for (const word of match[2].split(/\s+/)) if (known.has(word)) found.add(word);
73
+ return [...found];
74
+ }
75
+ //#endregion
76
+ //#region src/vite/css.ts
77
+ /**
78
+ * The CSS modules the plugin serves (PLAN §5.10, D13, D14) and the ids they
79
+ * live under. None exists on disk; `load` builds each from compiled atoms
80
+ * and rules.
81
+ * The ids end in `.css` and sit under the root, so Vite's CSS pipeline, HMR
82
+ * and framework CSS collection treat them as ordinary stylesheets.
83
+ */
84
+ const BASE_SPECIFIER = "virtual:vicss/base.css";
85
+ const SHEET_SPECIFIER = "virtual:vicss/sheet.css";
86
+ const MODULE_SUFFIX = ".vicss.css";
87
+ /** `root` is Vite's root with forward slashes. */
88
+ function cssIds(root) {
89
+ return {
90
+ base: path.posix.join(root, "@vicss/base.css"),
91
+ sheet: path.posix.join(root, "@vicss/sheet.css"),
92
+ tokens: path.posix.join(root, "@vicss/tokens.css"),
93
+ libraries: path.posix.join(root, "@vicss/lib/")
94
+ };
95
+ }
96
+ function splitQuery(id) {
97
+ const at = id.search(/[?#]/);
98
+ return at === -1 ? [id, ""] : [id.slice(0, at), id.slice(at)];
99
+ }
100
+ /**
101
+ * `./Button.tsx.vicss.css`: what a styled module imports in dev. A framework
102
+ * file's script sub-module (`Card.astro?astro&type=script…`) imports its
103
+ * file's, which holds the styles of every module of the file.
104
+ */
105
+ function moduleCssSpecifier(id) {
106
+ return `./${path.posix.basename(splitQuery(id)[0])}${MODULE_SUFFIX}`;
107
+ }
108
+ const join = (...parts) => parts.filter(Boolean).join("\n");
109
+ /**
110
+ * Dev `base.css` (PLAN §5.10, D14): everything but the atoms. Every token,
111
+ * so a token edit never waits for a module to use it; the layer statement
112
+ * over every atom compiled so far; the rules of every module compiled so far
113
+ * (themes after the root tokens, module keyframes after the config's); and
114
+ * the config's reset, fonts, keyframes and global CSS. Its `<style>` comes
115
+ * before any module's, and the browser orders layers by their first
116
+ * declaration in document order, so updating it re-orders the sub-layers of
117
+ * modules added later.
118
+ */
119
+ function devBaseCss(registry, options, libraries = []) {
120
+ return printBase({
121
+ ...options,
122
+ atoms: [
123
+ ...registry.layerAtoms(),
124
+ ...registry.paletteAtoms(),
125
+ ...libraries.flatMap((library) => library.atoms)
126
+ ],
127
+ rules: [...registry.rules(), ...libraries.flatMap((library) => library.rules)],
128
+ referenced: registry.paletteKeys(),
129
+ tokens: "all"
130
+ });
131
+ }
132
+ /** `virtual:vicss/lib/<n>.css`, the dev CSS of the library at index `n`. */
133
+ const LIBRARY_SPECIFIER = "virtual:vicss/lib/";
134
+ function librarySpecifier(index) {
135
+ return `${LIBRARY_SPECIFIER}${index}.css`;
136
+ }
137
+ /**
138
+ * Dev CSS of one module: its atom rules and flattened recipe bases, in the
139
+ * sub-layers `base.css` declares; its other rules print in `base.css`.
140
+ */
141
+ function moduleCss(styles, system, names) {
142
+ return printAtoms(styles.atoms, system, styles.rules, names);
143
+ }
144
+ /**
145
+ * The build's sheet and, in an SPA, its tokens file. Both depend only on the
146
+ * scan and the config, so every environment and build order prints the same
147
+ * bytes. Tokens are the used ones: those atoms, rules, `token()` calls,
148
+ * `globalCss` and config keyframes read (D13). Flattened recipe bases are
149
+ * code, like atoms: they print in the sheet, never in the tokens file.
150
+ */
151
+ function buildCss(scan, options, tokensFile) {
152
+ const { system, layers } = options;
153
+ const input = {
154
+ ...options,
155
+ atoms: scan.atoms,
156
+ rules: scan.rules,
157
+ tokens: "used",
158
+ referenced: scan.tokens
159
+ };
160
+ const tokenKeys = new Set(baseTokens(input).map((token) => `${token.scale}:${token.path}`));
161
+ if (!tokensFile) return {
162
+ sheet: printSheet(input),
163
+ tokens: void 0,
164
+ tokenKeys
165
+ };
166
+ return {
167
+ sheet: join(layers ? outerLayers(layers) : "", layerStatement(scan.atoms, system, scan.rules), printAtoms(scan.atoms, system, scan.rules, options.names)),
168
+ tokens: printBase(input),
169
+ tokenKeys
170
+ };
171
+ }
172
+ //#endregion
173
+ //#region src/vite/attributes.ts
174
+ /**
175
+ * What every compiled component and JSX module gets for `class=` and
176
+ * `style=` (PLAN §5.16), whether it imports vicss or not: a style it
177
+ * receives may come from anywhere.
178
+ *
179
+ * - Vue's `normalizeClass` and Svelte's `clsx` read an object as a map from
180
+ * class to condition, so `class={card}` would write the merge ids. The
181
+ * compiled component's calls go through `__vxClassWith`, which passes a
182
+ * compiled style as its class string (runtime/style.ts), and the calls
183
+ * that normalise a props object's `class` (spreads, a root element's
184
+ * attributes) through `__vxPropsWith` and `__vxEffectWith`.
185
+ * - In dev, a `style=` value the compiler could not write as an inline
186
+ * style goes through `__vxStyleCheck`, which fails on a compiled style
187
+ * and names the fix (runtime/dev.ts): in JSX attributes and template
188
+ * literals here, in Vue's, Svelte's and Astro's style setters through
189
+ * `__vxStyleWith`.
190
+ */
191
+ const RUNTIME = "@menapo/vicss";
192
+ /** Vue's and Svelte's class calls, and in dev the style setters of all three compilers. */
193
+ function rewriteComponent(code, id, options) {
194
+ const edits = new SourceEdits(code);
195
+ const helpers = /* @__PURE__ */ new Set();
196
+ const consts = [];
197
+ let n = 0;
198
+ const src = JSON.stringify(options.src);
199
+ const named = /import\s*\{([^}]*)\}\s*from\s*(["'])([^"']+)\2;?/g;
200
+ const WRAPS = {
201
+ normalizeClass: (local) => `__vxClassWith(${local})`,
202
+ ssrRenderClass: (local) => `__vxClassWith(${local})`,
203
+ mergeProps: (local) => `__vxPropsWith(${local})`,
204
+ normalizeProps: (local) => `__vxPropsWith(${local})`,
205
+ ssrRenderAttrs: (local) => `__vxPropsWith(${local})`,
206
+ normalizeStyle: (local) => options.dev ? `__vxStyleWith(${local}, 0, ${src})` : void 0,
207
+ ssrRenderStyle: (local) => options.dev ? `__vxStyleWith(${local}, 0, ${src})` : void 0,
208
+ addAttribute: (local) => options.dev ? `__vxStyleWith(${local}, 0, ${src}, "style")` : void 0
209
+ };
210
+ const SOURCES = /^(?:vue|vue\/server-renderer|@vue\/server-renderer|astro\/(?:runtime\/server(?:\/index\.js)?|compiler-runtime))$/;
211
+ for (const match of code.matchAll(named)) {
212
+ if (!SOURCES.test(match[3])) continue;
213
+ const listStart = match.index + match[0].indexOf("{") + 1;
214
+ for (const spec of match[1].matchAll(/(\w+)(?:\s+as\s+([\w$]+))?/g)) {
215
+ const wrap = WRAPS[spec[1]];
216
+ const local = spec[2] ?? spec[1];
217
+ const inner = `__vxOwn${++n}`;
218
+ const text = wrap?.(inner);
219
+ if (!text) continue;
220
+ edits.overwrite(listStart + spec.index, listStart + spec.index + spec[0].length, `${spec[1]} as ${inner}`);
221
+ for (const helper of text.match(/__vx(?:ClassWith|StyleWith|PropsWith)/g)) helpers.add(helper);
222
+ consts.push({
223
+ at: match.index + match[0].length,
224
+ text: `const ${local} = ${text};`
225
+ });
226
+ }
227
+ }
228
+ for (const match of code.matchAll(/import\s*\*\s*as\s+([\w$]+)\s+from\s*(["'])svelte\/internal\/(client|server)\2;?/g)) {
229
+ const ns = match[1];
230
+ const calls = {
231
+ clsx: "__vxClassWith",
232
+ attribute_effect: "__vxEffectWith",
233
+ attributes: "__vxPropsWith",
234
+ set_style: options.dev ? "__vxStyleWith" : void 0,
235
+ attr_style: options.dev ? "__vxStyleWith" : void 0
236
+ };
237
+ for (const [name, helper] of Object.entries(calls)) {
238
+ if (!helper) continue;
239
+ const pattern = new RegExp(`(?<![\\w$.])${ns.replace(/\$/g, "\\$")}\\.${name}(?=\\s*\\()`, "g");
240
+ const local = `__vx_${name}`;
241
+ let used = false;
242
+ for (const call of code.matchAll(pattern)) {
243
+ edits.overwrite(call.index, call.index + call[0].length, local);
244
+ used = true;
245
+ }
246
+ if (!used) continue;
247
+ helpers.add(helper);
248
+ const at = name === "set_style" ? `, 1, ${src}` : name === "attr_style" ? `, 0, ${src}` : "";
249
+ consts.push({
250
+ at: match.index + match[0].length,
251
+ text: `const ${local} = ${helper}(${ns}.${name}${at});`
252
+ });
253
+ }
254
+ }
255
+ if (consts.length === 0) return null;
256
+ for (const { at, text } of consts) edits.insert(at, `\n${text}`);
257
+ edits.prepend(`import { ${[...helpers].join(", ")} } from "${RUNTIME}";\n`);
258
+ const file = id.replace(/[?#].*$/, "");
259
+ return {
260
+ code: edits.toString(),
261
+ map: edits.generateMap({
262
+ source: file,
263
+ file,
264
+ includeContent: true,
265
+ hires: true
266
+ })
267
+ };
268
+ }
269
+ /** Values whose type is plainly not a compiled style: no check. */
270
+ const PLAIN = /* @__PURE__ */ new Set([
271
+ "ObjectExpression",
272
+ "Literal",
273
+ "TemplateLiteral",
274
+ "ArrowFunctionExpression",
275
+ "FunctionExpression"
276
+ ]);
277
+ /**
278
+ * Dev: `style={x}` in JSX, and `style=${x}` in a template literal, where
279
+ * `x` is anything the compiler left: checked when it renders.
280
+ */
281
+ function checkStyles(code, id, src) {
282
+ const file = id.replace(/[?#].*$/, "");
283
+ const lang = /\.([cm]?[jt]sx?)$/.exec(file)?.[1]?.replace(/^[cm]/, "");
284
+ let program;
285
+ try {
286
+ const parsed = parseSync(file, code, {
287
+ lang: lang ?? "js",
288
+ sourceType: "module"
289
+ });
290
+ if (parsed.errors.length > 0) return null;
291
+ program = parsed.program;
292
+ } catch {
293
+ return null;
294
+ }
295
+ const values = [];
296
+ const visit = (node) => {
297
+ if (!node || typeof node !== "object") return;
298
+ if (Array.isArray(node)) {
299
+ for (const item of node) visit(item);
300
+ return;
301
+ }
302
+ const current = node;
303
+ if (typeof current.type !== "string") return;
304
+ if (current.type === "JSXAttribute" && current.name.type === "JSXIdentifier" && current.name.name === "style" && current.value?.type === "JSXExpressionContainer" && current.value.expression.type !== "JSXEmptyExpression" && !PLAIN.has(current.value.expression.type)) values.push(current.value.expression);
305
+ else if (current.type === "TemplateLiteral") current.quasis.forEach((quasi, i) => {
306
+ const expression = current.expressions[i];
307
+ if (expression && /\sstyle="?$/.test(quasi.value.raw) && !PLAIN.has(expression.type)) values.push(expression);
308
+ });
309
+ for (const key in current) if (key !== "parent" && key !== "range") visit(current[key]);
310
+ };
311
+ visit(program);
312
+ if (values.length === 0) return null;
313
+ const edits = new SourceEdits(code, { keepLines: true });
314
+ const locate = createLocator(code);
315
+ for (const value of values) {
316
+ const at = JSON.stringify(`${src}:${locate(value.start).line}`);
317
+ edits.insert(value.start, "__vxStyleCheck(");
318
+ edits.insert(value.end, `, ${at})`);
319
+ }
320
+ const first = program.body.find((node) => !(node.type === "ExpressionStatement" && typeof node.directive === "string"));
321
+ edits.insert(first?.start ?? 0, `import { __vxStyleCheck } from "${RUNTIME}"; `);
322
+ return {
323
+ code: edits.toString(),
324
+ map: edits.generateMap({
325
+ source: file,
326
+ file,
327
+ includeContent: true,
328
+ hires: true
329
+ })
330
+ };
331
+ }
332
+ //#endregion
333
+ //#region src/vite/html.ts
334
+ /**
335
+ * The SPA tokens file (PLAN D13). Tokens change on a theme-only deploy and
336
+ * atoms do not; kept out of every chunk's `importedCss`, they rename no JS
337
+ * file through `augmentChunkHash` (report 06 §7.1). Only HTML that Vite
338
+ * builds can link it, so it exists only when the client build has an HTML
339
+ * entry, and only when Vite splits CSS per chunk: with one CSS file for the
340
+ * whole app, no JS names CSS at all.
341
+ */
342
+ /** The CSS entry's name: Vite emits its CSS as `assets/vicss-tokens-[hash].css`. */
343
+ const TOKENS_CHUNK_NAME = "vicss-tokens";
344
+ /**
345
+ * Whether the client build has an HTML entry, read from the config so that
346
+ * every environment agrees: it decides what the sheet holds. Mirrors how
347
+ * Vite picks a build's input.
348
+ */
349
+ function hasHtmlEntry(config) {
350
+ const client = config.environments.client;
351
+ if (!client || client.build.lib) return false;
352
+ const input = client.build.rolldownOptions?.input ?? client.input;
353
+ if (input === void 0) return existsSync(path.join(config.root, "index.html"));
354
+ return (typeof input === "string" ? [input] : Object.values(input)).some((entry) => entry.endsWith(".html"));
355
+ }
356
+ /**
357
+ * `<link rel="stylesheet">` first in `<head>`, so its layer statement comes
358
+ * first. Its URL is built the way Vite builds its own links in the same page
359
+ * (`renderBuiltUrl`, then the base, relative when the base is), and it is
360
+ * `crossorigin` like them, so a CDN setup serves both alike.
361
+ */
362
+ function tokensLink(config, fileName, hostId) {
363
+ const link = (href) => ({
364
+ tag: "link",
365
+ attrs: {
366
+ rel: "stylesheet",
367
+ crossorigin: true,
368
+ href
369
+ },
370
+ injectTo: "head-prepend"
371
+ });
372
+ const rendered = config.experimental.renderBuiltUrl?.(fileName, {
373
+ hostId,
374
+ hostType: "html",
375
+ type: "asset",
376
+ ssr: false
377
+ });
378
+ if (typeof rendered === "string") return link(rendered);
379
+ if (!(rendered?.relative ?? (config.base === "" || config.base === "./"))) return link(`${config.base}${fileName}`);
380
+ const href = path.posix.join(path.posix.relative(path.posix.dirname(hostId), ""), fileName);
381
+ return link(href.startsWith(".") ? href : `./${href}`);
382
+ }
383
+ //#endregion
384
+ //#region src/vite/registry.ts
385
+ /**
386
+ * What each compiled module contributes to the CSS, shared by every
387
+ * environment: one source compiles to the same atoms for the client, SSR and
388
+ * any other consumer, so keeping one copy also keeps their CSS identical.
389
+ */
390
+ /** Whether a module contributes CSS, and so imports it. */
391
+ function isStyled(styles) {
392
+ return styles.atoms.length > 0 || styles.tokens.length > 0 || styles.rules.length > 0;
393
+ }
394
+ /** Entries counted by key, so one leaves only when the last module holding it does. */
395
+ var Counted = class {
396
+ #entries = /* @__PURE__ */ new Map();
397
+ /** Returns the keys that were not held before. */
398
+ add(values, keyOf) {
399
+ const added = [];
400
+ for (const value of values) {
401
+ const key = keyOf(value);
402
+ const entry = this.#entries.get(key);
403
+ if (entry) entry.count++;
404
+ else {
405
+ this.#entries.set(key, {
406
+ value,
407
+ count: 1
408
+ });
409
+ added.push(key);
410
+ }
411
+ }
412
+ return added;
413
+ }
414
+ /** Returns the keys that lost their last holder. */
415
+ remove(values, keyOf) {
416
+ const gone = [];
417
+ for (const value of values) {
418
+ const key = keyOf(value);
419
+ const entry = this.#entries.get(key);
420
+ if (!entry || --entry.count > 0) continue;
421
+ this.#entries.delete(key);
422
+ gone.push(key);
423
+ }
424
+ return gone;
425
+ }
426
+ has(key) {
427
+ return this.#entries.has(key);
428
+ }
429
+ values() {
430
+ return [...this.#entries.values()].map((entry) => entry.value);
431
+ }
432
+ clear() {
433
+ this.#entries.clear();
434
+ }
435
+ };
436
+ var Registry = class {
437
+ #modules = /* @__PURE__ */ new Map();
438
+ /**
439
+ * Module ids by file: a framework file can be several modules (Astro's
440
+ * `?astro&type=script` scripts next to its component), which share the
441
+ * file's one dev CSS module.
442
+ */
443
+ #files = /* @__PURE__ */ new Map();
444
+ #dependants = /* @__PURE__ */ new Map();
445
+ /**
446
+ * What each compiled module that styles nothing read: `sx(row)` folds a
447
+ * marker's class, a module may inline a constant into a string. It must
448
+ * compile again when one of those files changes, as styled modules do.
449
+ */
450
+ #reads = /* @__PURE__ */ new Map();
451
+ /**
452
+ * One atom per sub-layer (chain and tier). The layer statement depends only
453
+ * on which sub-layers exist, so it changes exactly when a key appears or
454
+ * goes, and one atom per key is enough to print it.
455
+ */
456
+ #subLayers = new Counted();
457
+ /** Rules by id: a theme folded into several modules prints once. */
458
+ #rules = new Counted();
459
+ /**
460
+ * `colorPalette` atoms and the palette keys styles read (`palette.600`):
461
+ * `base.css` prints the palettes' rules from them (core/palettes.ts).
462
+ */
463
+ #palettes = new Counted();
464
+ #paletteKeys = new Counted();
465
+ /**
466
+ * Flattened recipe bases by id, and one of their atoms per chain. They
467
+ * print in the modules' CSS, so `base.css` depends only on the chains'
468
+ * flattened sub-layers, as on the atoms' sub-layers: an edited base is a
469
+ * new class over the same chains, which leaves it alone.
470
+ */
471
+ #bases = new Counted();
472
+ #flatLayers = new Counted();
473
+ get(id) {
474
+ return this.#modules.get(id);
475
+ }
476
+ has(id) {
477
+ return this.#modules.has(id);
478
+ }
479
+ ids() {
480
+ return [...this.#modules.keys()];
481
+ }
482
+ /** The styles of every module of `file` (an id without its query), merged. */
483
+ ofFile(file) {
484
+ const styles = [...this.#files.get(file) ?? []].flatMap((id) => this.#modules.get(id) ?? []);
485
+ if (styles.length <= 1) return styles[0];
486
+ return {
487
+ atoms: [...new Map(styles.flatMap((s) => s.atoms).map((a) => [a.className, a])).values()].sort((a, b) => compareStrings(a.className, b.className)),
488
+ tokens: styles.flatMap((s) => s.tokens),
489
+ rules: styles.flatMap((s) => s.rules),
490
+ dependencies: [...new Set(styles.flatMap((s) => s.dependencies))]
491
+ };
492
+ }
493
+ /**
494
+ * Returns whether `base.css` changed: a sub-layer, and so the layer
495
+ * statement, or a rule appeared or went.
496
+ */
497
+ set(id, styles) {
498
+ const removed = this.#remove(id);
499
+ this.#modules.set(id, styles);
500
+ const file = fileOf(id);
501
+ let ids = this.#files.get(file);
502
+ if (!ids) this.#files.set(file, ids = /* @__PURE__ */ new Set());
503
+ ids.add(id);
504
+ this.#depend(id, styles.dependencies);
505
+ this.#bases.add(styles.rules.filter(isBase), ruleId);
506
+ const added = [
507
+ ...this.#subLayers.add(styles.atoms, subLayer).map((key) => `l:${key}`),
508
+ ...this.#rules.add(styles.rules.filter(prints), ruleId).map((key) => `r:${key}`),
509
+ ...this.#flatLayers.add(flatAtoms(styles.rules), flatLayer).map((key) => `f:${key}`),
510
+ ...this.#palettes.add(styles.atoms.filter(isPaletteAtom), className).map((key) => `p:${key}`),
511
+ ...this.#paletteKeys.add(paletteRefs(styles), refId).map((key) => `k:${key}`)
512
+ ];
513
+ return removed.some((key) => !this.#holds(key)) || added.some((key) => !removed.includes(key));
514
+ }
515
+ /** Returns whether `base.css` changed. */
516
+ delete(id) {
517
+ return this.#remove(id).length > 0;
518
+ }
519
+ /**
520
+ * A compiled module that styles nothing: not registered, but a module
521
+ * that read `dependencies`, so `dependantsOf` lists it. Returns whether
522
+ * `base.css` changed (it styled something before).
523
+ */
524
+ unstyled(id, dependencies) {
525
+ const changed = this.delete(id);
526
+ this.#depend(id, dependencies);
527
+ return changed;
528
+ }
529
+ clear() {
530
+ this.#modules.clear();
531
+ this.#files.clear();
532
+ this.#dependants.clear();
533
+ this.#reads.clear();
534
+ this.#subLayers.clear();
535
+ this.#rules.clear();
536
+ this.#palettes.clear();
537
+ this.#paletteKeys.clear();
538
+ this.#bases.clear();
539
+ this.#flatLayers.clear();
540
+ }
541
+ /** Modules, styled or not, that read constants from `file`. */
542
+ dependantsOf(file) {
543
+ return [...this.#dependants.get(file) ?? []];
544
+ }
545
+ /** One atom per sub-layer in use: what the layer statement is printed from. */
546
+ layerAtoms() {
547
+ return this.#subLayers.values();
548
+ }
549
+ /** Every registered module's rules, one per id: flattened bases declare their sub-layers. */
550
+ rules() {
551
+ return [...this.#rules.values(), ...this.#bases.values()];
552
+ }
553
+ /** Every `colorPalette` atom of the registered modules. */
554
+ paletteAtoms() {
555
+ return this.#palettes.values();
556
+ }
557
+ /** The palette keys the registered modules read, as token references. */
558
+ paletteKeys() {
559
+ return this.#paletteKeys.values();
560
+ }
561
+ /** Records that `id` read `dependencies`, replacing what it read before. */
562
+ #depend(id, dependencies) {
563
+ for (const dep of this.#reads.get(id) ?? []) {
564
+ const set = this.#dependants.get(dep);
565
+ set?.delete(id);
566
+ if (set?.size === 0) this.#dependants.delete(dep);
567
+ }
568
+ if (dependencies.length === 0) this.#reads.delete(id);
569
+ else this.#reads.set(id, dependencies);
570
+ for (const dep of dependencies) {
571
+ let set = this.#dependants.get(dep);
572
+ if (!set) this.#dependants.set(dep, set = /* @__PURE__ */ new Set());
573
+ set.add(id);
574
+ }
575
+ }
576
+ #holds(key) {
577
+ const id = key.slice(2);
578
+ switch (key[0]) {
579
+ case "l": return this.#subLayers.has(id);
580
+ case "r": return this.#rules.has(id);
581
+ case "f": return this.#flatLayers.has(id);
582
+ case "p": return this.#palettes.has(id);
583
+ default: return this.#paletteKeys.has(id);
584
+ }
585
+ }
586
+ /**
587
+ * Returns the sub-layers (`l:`), rules (`r:`), flattened sub-layers (`f:`),
588
+ * palette atoms (`p:`) and palette keys (`k:`) that lost their last holder.
589
+ */
590
+ #remove(id) {
591
+ const old = this.#modules.get(id);
592
+ if (!old) return [];
593
+ this.#modules.delete(id);
594
+ const ids = this.#files.get(fileOf(id));
595
+ ids?.delete(id);
596
+ if (ids?.size === 0) this.#files.delete(fileOf(id));
597
+ this.#depend(id, []);
598
+ this.#bases.remove(old.rules.filter(isBase), ruleId);
599
+ return [
600
+ ...this.#subLayers.remove(old.atoms, subLayer).map((key) => `l:${key}`),
601
+ ...this.#rules.remove(old.rules.filter(prints), ruleId).map((key) => `r:${key}`),
602
+ ...this.#flatLayers.remove(flatAtoms(old.rules), flatLayer).map((key) => `f:${key}`),
603
+ ...this.#palettes.remove(old.atoms.filter(isPaletteAtom), className).map((key) => `p:${key}`),
604
+ ...this.#paletteKeys.remove(paletteRefs(old), refId).map((key) => `k:${key}`)
605
+ ];
606
+ }
607
+ };
608
+ /** The palette keys a module's styles read: colour tokens of the `palette` group. */
609
+ function paletteRefs(styles) {
610
+ const refs = [
611
+ ...styles.atoms.flatMap((atom) => atom.tokens),
612
+ ...styles.rules.flatMap((rule) => rule.tokens),
613
+ ...styles.tokens
614
+ ];
615
+ const unique = /* @__PURE__ */ new Map();
616
+ for (const ref of refs) if (ref.scale === "colors" && (ref.path === "palette" || ref.path.startsWith("palette."))) unique.set(ref.path, ref);
617
+ return [...unique.values()];
618
+ }
619
+ const subLayer = (atom) => `${atom.chain.join(" ")}|${atom.tier}`;
620
+ const className = (atom) => atom.className;
621
+ const refId = (ref) => ref.path;
622
+ const flatLayer = (atom) => atom.chain.join(" ");
623
+ const isBase = (rule) => rule.kind === "base";
624
+ /** What `base.css` prints: themes and keyframes. */
625
+ const prints = (rule) => !isBase(rule);
626
+ const flatAtoms = (rules) => rules.flatMap((rule) => isBase(rule) ? rule.atoms ?? [] : []);
627
+ /** A module id without its query: the file its dev CSS module is named after. */
628
+ const fileOf = (id) => id.replace(/[?#].*$/, "");
629
+ const ruleId = (rule) => rule.id;
630
+ //#endregion
631
+ //#region src/vite/plugin.ts
632
+ /**
633
+ * The Vite plugin (PLAN §5.10, M2). One `pre` transform compiles styled
634
+ * modules on their source; dev serves one CSS module per styled module plus
635
+ * a shared `base.css` (D14); a build pre-scans `include` and serves one sheet
636
+ * (D5), plus a tokens file in SPAs (D13).
637
+ */
638
+ const RUNTIME_PACKAGE = "@menapo/vicss";
639
+ /** What a library build writes into its `outDir` (PLAN §5.13). */
640
+ const MANIFEST_FILE = "vicss.json";
641
+ const LIBRARY_SHEET_FILE = "vicss.css";
642
+ const SCRIPT_ID = /\.[cm]?[jt]sx?$/;
643
+ /** The calls `vicss:attributes` rewrites: a module with none is left alone. */
644
+ const COMPONENT_CALLS = [
645
+ "normalizeClass",
646
+ "ssrRenderClass",
647
+ "mergeProps",
648
+ "normalizeProps",
649
+ "ssrRenderAttrs",
650
+ "clsx",
651
+ "attribute_effect",
652
+ "attributes",
653
+ "normalizeStyle",
654
+ "ssrRenderStyle",
655
+ "set_style",
656
+ "attr_style",
657
+ "addAttribute"
658
+ ];
659
+ /** Declaration files: never styled, and the generated `vicss.d.ts` names the runtime. */
660
+ const DECLARATION_ID = /\.d\.[cm]?ts$/;
661
+ /**
662
+ * PLAN §6: a warning above this share of hashed atoms (values outside the
663
+ * scales, and long or unusual literals), as a design system erodes.
664
+ */
665
+ const HASHED_BUDGET = .1;
666
+ const CSS_ID = /(?:^virtual:vicss\/(?:base|sheet|lib\/\d+)\.css|\/@vicss\/(?:base|sheet|tokens|lib\/\d+)\.css|\.vicss\.css)(?:[?#]|$)/;
667
+ /** Modules compiled at once outside a transform (HMR, a config edit). */
668
+ const BATCH = 16;
669
+ function vicss(options = {}) {
670
+ let state;
671
+ let server;
672
+ const registry = new Registry();
673
+ const cache = new CompileCache();
674
+ /** Dev: ids the transform rewrote, invalidated when the config changes. */
675
+ const transformed = /* @__PURE__ */ new Set();
676
+ /**
677
+ * Dev: what each framework file's scripts style, as the pre-scan reads
678
+ * them, by file and the hash of its source.
679
+ */
680
+ const scriptStyles = /* @__PURE__ */ new Map();
681
+ /**
682
+ * Build: one scan per config, shared by every environment of one build.
683
+ * A build is the configs one `vite build` or builder resolves, all before
684
+ * any environment starts; a config resolved after that starts a new one.
685
+ */
686
+ const scans = /* @__PURE__ */ new Map();
687
+ let session = newSession();
688
+ /** The CSS each scan's result prints with the libraries, its atom count and its classes. */
689
+ const sheets = /* @__PURE__ */ new WeakMap();
690
+ /** Each scan's classes in literal form, for `api.literals()`. */
691
+ const literalTables = /* @__PURE__ */ new WeakMap();
692
+ /** A scan whose sheet failed to print (a library's atoms conflict with the app's). */
693
+ const sheetErrors = /* @__PURE__ */ new WeakMap();
694
+ /** Scan failures a hook has reported (`sheetFor`). */
695
+ const reportedFailures = /* @__PURE__ */ new WeakSet();
696
+ const reported = /* @__PURE__ */ new WeakSet();
697
+ /**
698
+ * What the scan (build) or an HMR pass (dev) compiled, by id, so the
699
+ * transform of an unchanged module reuses it instead of compiling every
700
+ * module twice. Build entries go when the build ends; dev entries when
701
+ * their transform takes them.
702
+ */
703
+ const kept = /* @__PURE__ */ new Map();
704
+ let diskCache;
705
+ /** Build: the CSS files of the chunk that holds the sheet, per environment. */
706
+ const sheetFiles = /* @__PURE__ */ new WeakMap();
707
+ /**
708
+ * Build: resolutions per environment, by importer directory, shared by
709
+ * the scan and the transforms: most modules import the same few modules.
710
+ */
711
+ const buildResolutions = /* @__PURE__ */ new WeakMap();
712
+ /** The SPA tokens file, per client build, with the config that links it. */
713
+ const tokenAssets = /* @__PURE__ */ new Map();
714
+ /**
715
+ * Dev: resolutions per environment, kept until a file is added or removed.
716
+ * While the watcher handles a change, every async call (Vite's native
717
+ * resolver included) waits behind it for ~50 ms; the requests of an
718
+ * edited module were all resolved when it was first served.
719
+ */
720
+ const resolutions = /* @__PURE__ */ new Map();
721
+ /** One recompile per file change, shared by the environments' `hotUpdate` calls. */
722
+ const changes = /* @__PURE__ */ new Map();
723
+ /**
724
+ * Dev and build watch mode: files a config load read before it failed (a
725
+ * token file it could not parse), watched too so fixing one reloads the
726
+ * config.
727
+ */
728
+ let failedReads = /* @__PURE__ */ new Set();
729
+ /**
730
+ * Build watch mode: a file the config read changed, so the next build
731
+ * loads the config again. A rebuild runs no `configResolved`.
732
+ */
733
+ let configChanged = false;
734
+ /** Build: `scan.json` writes in progress, which the build's end waits for. */
735
+ const cacheWrites = [];
736
+ /**
737
+ * Build: scan threads started while the config loads (pool.ts), for the
738
+ * scan to take; closed if it does not.
739
+ */
740
+ let spawned;
741
+ /** Settles when the current dev server closes. */
742
+ let closing = new Promise(() => {});
743
+ /** Dev: settles once the modules registered before a config edit compiled again. */
744
+ let rebuilt = Promise.resolve();
745
+ const sources = [RUNTIME_PACKAGE];
746
+ const ready = () => {
747
+ if (!state) throw new Error("vicss: the plugin was used before Vite resolved its config");
748
+ return state;
749
+ };
750
+ const layers = options.layers;
751
+ /**
752
+ * The config with the installed libraries it must cover (an app's), their
753
+ * manifests watched like the config's files. Throws VicssError when a
754
+ * library uses a token or condition the config lacks.
755
+ */
756
+ function checkLibraries(s, loaded) {
757
+ const libraries = s.library ? [] : loadLibraries(s.root, loaded.system);
758
+ const files = libraries.flatMap((library) => library.files);
759
+ return {
760
+ ...loaded,
761
+ watch: [.../* @__PURE__ */ new Set([...loaded.watch, ...files])],
762
+ configFiles: loaded.watch,
763
+ libraries
764
+ };
765
+ }
766
+ function setSystem(s, loaded) {
767
+ s.loaded = loaded;
768
+ s.compile = {
769
+ system: loaded.system,
770
+ importSources: loaded.system.config.importSources ?? [],
771
+ parsed: createParseCache(),
772
+ sourceMap: s.dev || Object.values(s.config.environments).some((env) => env.build.sourcemap),
773
+ dev: s.devSource,
774
+ placeholders: !s.dev,
775
+ keepLines: s.dev,
776
+ root: s.root,
777
+ package: s.pkg,
778
+ frameworks: s.frameworks
779
+ };
780
+ const warned = /* @__PURE__ */ new Set();
781
+ const options = loaded.system.classNames;
782
+ s.print = {
783
+ system: loaded.system,
784
+ layers,
785
+ warn(message) {
786
+ if (warned.has(message)) return;
787
+ warned.add(message);
788
+ s.config.logger.warn(`vicss: ${message}`);
789
+ },
790
+ ...s.dev && { names: createNamer([], {
791
+ prefix: options.prefix,
792
+ minLength: minLengthOf(options, false)
793
+ }, loaded.libraries.map((library) => ({
794
+ library: library.name,
795
+ names: library.classes
796
+ })), true) }
797
+ };
798
+ const short = minLengthWarning(options);
799
+ if (short) s.config.logger.warn(`vicss: ${short}`);
800
+ setConfigHash(s);
801
+ sources.splice(0, sources.length, .../* @__PURE__ */ new Set([RUNTIME_PACKAGE, ...s.compile.importSources]));
802
+ const dirs = loaded.libraries.flatMap((library) => library.dirs);
803
+ libraryDirs.splice(0, libraryDirs.length, ...dirs.length > 0 ? dirs.map((dir) => new RegExp(`^${escapeRegExp(dir)}/`)) : [NOTHING]);
804
+ writeTypes(s);
805
+ }
806
+ /**
807
+ * The generated `vicss.d.ts`, for the config just loaded (PLAN §5.11),
808
+ * with the framework of the project's plain modules, so `sx()`'s result
809
+ * type follows the shape the compiler gives them (§5.12); and next to it
810
+ * the lint snapshot, so the editor's lint rules see the config the server
811
+ * just loaded.
812
+ */
813
+ function writeTypes(s) {
814
+ if (options.types === false) return;
815
+ const types = typesFile(s.root, options.types?.output);
816
+ const outputs = [[types, () => generateTypes(s.loaded.system, { framework: s.frameworks.scripts })], [snapshotFile(types), () => snapshotText(s.loaded.system, { root: snapshotRoot(snapshotFile(types), s.root) })]];
817
+ for (const [file, text] of outputs) try {
818
+ if (writeIfChanged(file, text())) s.config.logger.info(`vicss: wrote ${path.relative(s.root, file) || file}`);
819
+ } catch (error) {
820
+ s.config.logger.warn(`vicss: could not write ${file}: ${String(error)}`);
821
+ }
822
+ }
823
+ /**
824
+ * The part of a cache key that stands for the config. The system's hash
825
+ * covers a function only by its source, so the files that define the
826
+ * config count too (a constant a utility closes over), and so do the
827
+ * token files it read, though their data is in the hash already. A
828
+ * function from an installed package (a preset's utility) is defined in a
829
+ * file Vite leaves to Node, which no list names: the lockfile stands for
830
+ * every such file, as for Vite's dependency optimizer, so an upgrade
831
+ * scans afresh. When no module on disk defines the config and a function
832
+ * could hide a change, `scan.json` is not used: token files hold no
833
+ * functions. The libraries' manifests do not count: no module's compile
834
+ * reads them, and the sheet prints them after the scan.
835
+ */
836
+ function setConfigHash(s) {
837
+ const defining = typeof options.config === "object" ? [s.config.configFile, ...s.config.configFileDependencies].flatMap((f) => f ? [normalizePath(f)] : []) : s.loaded.configFiles;
838
+ const functions = hasFunction(s.loaded.system.config);
839
+ const contents = [.../* @__PURE__ */ new Set([
840
+ ...defining,
841
+ ...s.loaded.configFiles,
842
+ ...functions ? lockfiles(s.root) : []
843
+ ])].map((file) => `${file}\0${readHash(file) ?? ""}`).join("\0");
844
+ const { scripts, jsxImportSource } = s.frameworks;
845
+ s.configHash = hashCode(`${s.loaded.system.hash}\0${s.compile.sourceMap}\0${s.compile.placeholders}\0${scripts}\0${jsxImportSource ?? ""}\0${s.pkg}\0${contents}`);
846
+ s.diskCache = defining.length > 0 || !functions;
847
+ }
848
+ async function reloadConfig(dev) {
849
+ const s = ready();
850
+ let loaded;
851
+ const reads = /* @__PURE__ */ new Set();
852
+ try {
853
+ loaded = checkLibraries(s, await loadSystem(s.root, options.config, (m) => s.config.logger.info(m), reads));
854
+ failedReads = /* @__PURE__ */ new Set();
855
+ } catch (error) {
856
+ failedReads = new Set([...reads].map(normalizePath));
857
+ dev.watcher.add([...failedReads]);
858
+ const failure = loadFailure(error, s.root, options.config);
859
+ const err = failure instanceof Error ? failure : new Error(String(failure));
860
+ const message = error instanceof VicssError ? err.message : `vicss: ${err.message}`;
861
+ s.config.logger.error(message, {
862
+ error: err,
863
+ timestamp: true
864
+ });
865
+ dev.environments.client?.hot.send({
866
+ type: "error",
867
+ err: {
868
+ message,
869
+ stack: err.stack ?? "",
870
+ plugin: "vicss"
871
+ }
872
+ });
873
+ return;
874
+ }
875
+ const before = sources.join("\n");
876
+ const styled = registry.ids();
877
+ const stale = [
878
+ ...[...transformed].map(fileOf),
879
+ ...styled.map((id) => fileOf(id) + MODULE_SUFFIX),
880
+ s.ids.base,
881
+ ...s.loaded.libraries.map((_, i) => `${s.ids.libraries}${i}.css`)
882
+ ];
883
+ setSystem(s, loaded);
884
+ registry.clear();
885
+ cache.clear();
886
+ kept.clear();
887
+ scans.clear();
888
+ transformed.clear();
889
+ scriptStyles.clear();
890
+ resolutions.clear();
891
+ dev.watcher.add([...loaded.watch]);
892
+ const client = dev.environments.client;
893
+ const resolve = memoize(resolutions, "client", envResolver(s, client));
894
+ const recompiled = inBatches(styled, async (id) => registry.has(id) ? void 0 : recompile(id, void 0, resolve));
895
+ rebuilt = recompiled;
896
+ const everything = sources.join("\n") !== before;
897
+ for (const env of Object.values(dev.environments)) {
898
+ const graph = env.moduleGraph;
899
+ if (everything) graph.invalidateAll();
900
+ else for (const file of stale) for (const mod of graph.getModulesByFile(file) ?? []) graph.invalidateModule(mod);
901
+ env.hot.send({
902
+ type: "full-reload",
903
+ path: "*"
904
+ });
905
+ }
906
+ await recompiled;
907
+ }
908
+ /**
909
+ * Build watch mode: the config again, before the rebuild that a change to
910
+ * one of its files started. A config that fails stays marked, so every
911
+ * rebuild reports it until it loads.
912
+ */
913
+ async function reloadBuildConfig(ctx, s) {
914
+ const reads = /* @__PURE__ */ new Set();
915
+ let loaded;
916
+ try {
917
+ loaded = checkLibraries(s, await loadSystem(s.root, options.config, (m) => s.config.logger.info(m), reads));
918
+ } catch (error) {
919
+ failedReads = new Set([...reads].map(normalizePath));
920
+ for (const file of [...s.loaded.watch, ...failedReads]) ctx.addWatchFile(file);
921
+ fail(ctx, error, {});
922
+ }
923
+ configChanged = false;
924
+ failedReads = /* @__PURE__ */ new Set();
925
+ setSystem(s, loaded);
926
+ cache.clear();
927
+ diskCache = void 0;
928
+ scans.clear();
929
+ kept.clear();
930
+ }
931
+ /** A hook context's resolver, through the dev server's memo. */
932
+ function devResolver(ctx) {
933
+ return memoize(resolutions, ctx.environment.name, contextResolver(ready(), ctx));
934
+ }
935
+ /** A hook context's resolver, through its build environment's memo. */
936
+ function buildResolver(ctx) {
937
+ let resolved = buildResolutions.get(ctx.environment);
938
+ if (!resolved) buildResolutions.set(ctx.environment, resolved = /* @__PURE__ */ new Map());
939
+ return memoize(resolved, "", contextResolver(ready(), ctx));
940
+ }
941
+ let refresh;
942
+ /**
943
+ * A module compiled after `base.css` was served brought new sub-layers.
944
+ * Stale copies go at once, so the next request prints the new statement;
945
+ * the browser's copy is replaced on the next tick, after a burst of
946
+ * transforms (a lazy route's modules) has settled.
947
+ */
948
+ function baseChanged() {
949
+ if (!server) return;
950
+ const base = ready().ids.base;
951
+ for (const env of Object.values(server.environments)) for (const mod of env.moduleGraph.getModulesByFile(base) ?? []) env.moduleGraph.invalidateModule(mod);
952
+ refresh ??= setTimeout(() => {
953
+ refresh = void 0;
954
+ const client = server?.environments.client;
955
+ for (const mod of client?.moduleGraph.getModulesByFile(base) ?? []) client.reloadModule(mod);
956
+ }, 30).unref();
957
+ }
958
+ /**
959
+ * A transform compiled other atoms than the registry held, after the
960
+ * module's CSS was served from them: two saves a few ms apart that the
961
+ * watcher reported once, or an HMR compile of a half-written file. The
962
+ * served copies go, and the browser's is replaced.
963
+ */
964
+ function cssChanged(id) {
965
+ for (const env of Object.values(server?.environments ?? {})) for (const mod of env.moduleGraph.getModulesByFile(fileOf(id) + ".vicss.css") ?? []) {
966
+ if (!mod.transformResult) continue;
967
+ env.moduleGraph.invalidateModule(mod);
968
+ if (env === server?.environments.client) env.reloadModule(mod);
969
+ }
970
+ }
971
+ /** Returns whether `base.css` changed. */
972
+ function register(id, styles) {
973
+ return isStyled(styles) ? registry.set(id, styles) : registry.unstyled(id, styles.dependencies);
974
+ }
975
+ /**
976
+ * Compiles `id` again outside its transform, from `code` or the file, and
977
+ * keeps the result for that transform. Returns whether the layer statement
978
+ * changed, or undefined when the module does not compile: its own
979
+ * transform runs again and reports why. The file is read synchronously:
980
+ * the first async read after a change waits behind the watcher for
981
+ * 15–85 ms (report 06 §6.2).
982
+ */
983
+ async function recompile(id, code, resolve) {
984
+ if (isSfc(id)) return void 0;
985
+ try {
986
+ const source = code ?? readFileSync(id, "utf8");
987
+ const result = compile(ready().compile, id, source, resolve);
988
+ kept.set(id, {
989
+ code: source,
990
+ result
991
+ });
992
+ return register(id, await result);
993
+ } catch {
994
+ return;
995
+ }
996
+ }
997
+ async function applyChange(file, type, read, resolve) {
998
+ const styled = [];
999
+ let base = false;
1000
+ if (SCRIPT_ID.test(file) && !file.includes("/node_modules/")) {
1001
+ if (type === "delete") {
1002
+ kept.delete(file);
1003
+ base = registry.delete(file);
1004
+ } else {
1005
+ const code = await read();
1006
+ if (registry.has(file) || sources.some((source) => code.includes(source))) {
1007
+ const changed = await recompile(file, code, resolve);
1008
+ if (changed !== void 0) {
1009
+ base ||= changed;
1010
+ styled.push(file);
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+ const dependants = registry.dependantsOf(file).filter((id) => id !== file);
1016
+ const changed = await inBatches(dependants, (id) => recompile(id, void 0, resolve));
1017
+ for (const [i, id] of dependants.entries()) {
1018
+ const layersChanged = changed[i];
1019
+ if (layersChanged === void 0) continue;
1020
+ base ||= layersChanged;
1021
+ styled.push(id);
1022
+ }
1023
+ return {
1024
+ styled,
1025
+ dependants,
1026
+ base
1027
+ };
1028
+ }
1029
+ /** The build's scan, started by the first environment that needs it. */
1030
+ function scanned(ctx) {
1031
+ return startScan(ctx).result;
1032
+ }
1033
+ function startScan(ctx) {
1034
+ const s = ready();
1035
+ const key = s.configHash;
1036
+ const running = scans.get(key);
1037
+ if (running) return running;
1038
+ let allQueued;
1039
+ const queued = new Promise((resolve) => allQueued = resolve);
1040
+ const cacheFile = s.diskCache && path.join(s.config.cacheDir, "vicss", "scan.json");
1041
+ const resolve = buildResolver(ctx);
1042
+ const resolveId = (source, importer) => resolveFile(resolve, source, importer);
1043
+ const setup = workerSetup(s);
1044
+ const promise = (async () => {
1045
+ if (cacheFile) await (diskCache ??= cache.read(cacheFile, s.root));
1046
+ const result = await scan({
1047
+ root: s.root,
1048
+ system: s.loaded.system,
1049
+ configHash: key,
1050
+ importSources: s.compile.importSources,
1051
+ cache,
1052
+ resolve: resolveId,
1053
+ compile: (id, code, script) => compile(s.compile, id, code, resolve, script),
1054
+ queued(id, code, result, prioritize) {
1055
+ kept.set(id, {
1056
+ code: hashCode(code),
1057
+ result,
1058
+ prioritize
1059
+ });
1060
+ },
1061
+ allQueued,
1062
+ cached(id, code, entry) {
1063
+ kept.set(id, {
1064
+ code: hashCode(code),
1065
+ result: Promise.resolve(compiledOf(entry, id, code))
1066
+ });
1067
+ },
1068
+ pool: setup ? (jobs) => {
1069
+ const pool = spawned ?? CompilePool.spawn(poolSize(jobs));
1070
+ spawned = void 0;
1071
+ return pool?.use({
1072
+ setup,
1073
+ resolve: resolveId,
1074
+ warn: (message) => s.config.logger.warn(`vicss: ${message}`)
1075
+ });
1076
+ } : void 0
1077
+ });
1078
+ if (cacheFile) {
1079
+ const write = async () => {
1080
+ await new Promise((resolve) => setImmediate(resolve));
1081
+ try {
1082
+ await cache.write(cacheFile, s.root, result.files);
1083
+ } catch (error) {
1084
+ s.config.logger.warn(`vicss: could not write ${cacheFile}: ${String(error)}`);
1085
+ }
1086
+ };
1087
+ cacheWrites.push(write());
1088
+ }
1089
+ return result;
1090
+ })();
1091
+ promise.then(allQueued, allQueued);
1092
+ const unused = () => {
1093
+ spawned?.close();
1094
+ spawned = void 0;
1095
+ };
1096
+ promise.then(unused, unused);
1097
+ const run = {
1098
+ queued,
1099
+ result: promise
1100
+ };
1101
+ scans.set(key, run);
1102
+ return run;
1103
+ }
1104
+ function sheetOf(result) {
1105
+ return printed(result).css;
1106
+ }
1107
+ function printed(result) {
1108
+ let sheet = sheets.get(result);
1109
+ if (!sheet) {
1110
+ const s = ready();
1111
+ const failed = sheetErrors.get(result);
1112
+ if (failed) throw failed.error;
1113
+ try {
1114
+ const all = withLibraries(result, s.loaded.libraries, s.loaded.system);
1115
+ const options = s.loaded.system.classNames;
1116
+ const names = createNamer(identitiesOf(all, result.names), {
1117
+ prefix: options.prefix,
1118
+ minLength: minLengthOf(options, s.library)
1119
+ }, s.loaded.libraries.map((library) => ({
1120
+ library: library.name,
1121
+ names: library.classes
1122
+ })), readableBuild(options));
1123
+ sheet = {
1124
+ css: buildCss(all, {
1125
+ ...s.print,
1126
+ names
1127
+ }, s.tokensFile),
1128
+ atoms: all.atoms.length,
1129
+ bases: all.rules.filter((rule) => rule.kind === "base").length,
1130
+ names,
1131
+ identities: identitiesOf(result, result.names).filter((identity) => !identity.startsWith("!"))
1132
+ };
1133
+ } catch (error) {
1134
+ sheetErrors.set(result, { error });
1135
+ throw error;
1136
+ }
1137
+ sheets.set(result, sheet);
1138
+ }
1139
+ return sheet;
1140
+ }
1141
+ /**
1142
+ * The build's scan and the CSS it prints, or their failure as this hook's
1143
+ * error. Every hook that waits for a failed scan sees the same error: the
1144
+ * first reports it, with its code frame, and the others get `undefined`
1145
+ * and go on quietly, so the build fails once.
1146
+ */
1147
+ async function sheetFor(ctx) {
1148
+ try {
1149
+ const scan = await scanned(ctx);
1150
+ const { css, names } = printed(scan);
1151
+ return {
1152
+ scan,
1153
+ css,
1154
+ names
1155
+ };
1156
+ } catch (error) {
1157
+ if (typeof error === "object" && error !== null) {
1158
+ if (reportedFailures.has(error)) return void 0;
1159
+ reportedFailures.add(error);
1160
+ }
1161
+ fail(ctx, error, { id: error instanceof ScanConflict ? error.id : void 0 });
1162
+ }
1163
+ }
1164
+ /**
1165
+ * The build's one summary line (spec P12), with the sizes of the files
1166
+ * written, or else of the sheet vicss printed.
1167
+ */
1168
+ function summarize(result, files) {
1169
+ if (reported.has(result)) return;
1170
+ reported.add(result);
1171
+ const kb = (n) => `${(n / 1e3).toFixed(2)} kB`;
1172
+ const size = (text) => `${kb(Buffer.byteLength(text))} │ gzip: ${kb(gzipSync(text).length)}`;
1173
+ const sizes = files ? files.map(([name, text]) => `${name} ${size(text)}`) : [`sheet ${size(sheetOf(result).sheet)} (unminified)`];
1174
+ const { logger } = ready().config;
1175
+ const own = new Set(result.atoms.map((atom) => atom.className));
1176
+ const every = [...result.atoms, ...baseAtoms(result.rules).filter((atom) => !own.has(atom.className))];
1177
+ const hashed = every.filter(isArbitrary).length;
1178
+ const share = every.length === 0 ? 0 : hashed / every.length;
1179
+ const percent = `${(share * 100).toFixed(1)} %`;
1180
+ const { atoms, bases } = printed(result);
1181
+ const flat = bases === 0 ? "" : `, ${bases} flattened base${bases === 1 ? "" : "s"}`;
1182
+ logger.info(`vicss: ${atoms} atoms${flat}, ${result.files.length} files scanned; ${result.arbitrary} values outside the scales, ${hashed} hashed atoms (${percent}); ${sizes.join(", ")}`);
1183
+ if (share > HASHED_BUDGET) logger.warn(`vicss: ${percent} of the atoms (${hashed} of ${every.length}) get hashed class names (\`~…\`): values outside the scales and long or unusual literals; the budget is ${HASHED_BUDGET * 100} %. \`vicss check\` lists them by file.`);
1184
+ }
1185
+ /**
1186
+ * Compiles a module (a script, or a framework file's compiled output),
1187
+ * registers its styles (dev) or checks them against the sheet (build), and
1188
+ * imports their CSS.
1189
+ */
1190
+ async function transformStyled(ctx, code, id) {
1191
+ if (id.includes("/node_modules/") || !sources.some((source) => code.includes(source))) return null;
1192
+ const s = ready();
1193
+ if (!s.dev) await startScan(ctx).queued;
1194
+ const reused = kept.get(id);
1195
+ let result;
1196
+ /** Whether the scan compiled it, so its atoms, rules and tokens are the sheet's. */
1197
+ let fromScan = false;
1198
+ try {
1199
+ const resolve = s.dev ? devResolver(ctx) : buildResolver(ctx);
1200
+ if (reused && reused.code === (s.dev ? code : hashCode(code))) {
1201
+ if (s.dev) kept.delete(id);
1202
+ reused.prioritize?.();
1203
+ const earlier = await reused.result.catch(async (error) => {
1204
+ if (!await sheetFor(ctx)) return void 0;
1205
+ throw error;
1206
+ });
1207
+ if (!earlier) return null;
1208
+ const file = (source, importer) => resolveFile(resolve, source, importer);
1209
+ if (s.dev || await resolvesAlike(earlier.resolutions, file)) {
1210
+ result = earlier;
1211
+ fromScan = !s.dev;
1212
+ }
1213
+ }
1214
+ const lines = s.compile.dev && isSfc(id) ? mappedLines(ctx, id) : void 0;
1215
+ result ??= await compile(s.compile, id, code, resolve, void 0, lines);
1216
+ } catch (error) {
1217
+ if (isSfc(id) && error instanceof VicssError && !(error instanceof ScanConflict)) {
1218
+ await failInComponent(ctx, s, id, error);
1219
+ return null;
1220
+ }
1221
+ fail(ctx, error, error instanceof ScanConflict ? { id: error.id } : {
1222
+ id,
1223
+ code
1224
+ });
1225
+ }
1226
+ for (const message of result.warnings) ctx.warn(message);
1227
+ for (const dep of result.dependencies) ctx.addWatchFile(dep);
1228
+ const styled = isStyled(result);
1229
+ if (s.dev) {
1230
+ if (styled && isSfc(id)) await checkTemplate(ctx, s, id, result);
1231
+ const before = registry.get(id);
1232
+ if (register(id, result)) baseChanged();
1233
+ if (before && before !== result && !sameCss(before, result)) cssChanged(id);
1234
+ if (!result.changed) return null;
1235
+ transformed.add(id);
1236
+ } else {
1237
+ if (!result.changed) return null;
1238
+ if (styled && !fromScan) {
1239
+ const sheet = await sheetFor(ctx);
1240
+ if (!sheet) return null;
1241
+ checkSheet(ctx, sheet.scan, sheet.css.tokenKeys, result, id, s.root);
1242
+ }
1243
+ }
1244
+ let out = result.code;
1245
+ if (styled) out += cssImports(s, id);
1246
+ return {
1247
+ code: out,
1248
+ map: result.map
1249
+ };
1250
+ }
1251
+ /**
1252
+ * A framework file's compiled output did not compile. Its location is in
1253
+ * code the author never wrote (the virtual script module in a build), so
1254
+ * the error is reported against the component. A build's scan compiled
1255
+ * the file's scripts, and a failure there is the build's one error, which
1256
+ * the first hook to see it reports (`sheetFor`): this returns quietly
1257
+ * once another has. In dev the scripts compile again, as `checkTemplate`
1258
+ * compiles them, and their error is reported at its place in the file.
1259
+ * What fails only in the compiled output (a style in the template) is
1260
+ * located through the framework's source map, by line.
1261
+ */
1262
+ async function failInComponent(ctx, s, id, error) {
1263
+ if (!s.dev && !await sheetFor(ctx)) return;
1264
+ const file = fileOf(id);
1265
+ const source = readSource(file);
1266
+ if (source === void 0) fail(ctx, error, { id });
1267
+ if (s.dev) {
1268
+ const resolve = devResolver(ctx);
1269
+ try {
1270
+ await Promise.all(extractScripts(file, source).map(({ code: text, ...script }) => compile(s.compile, file, text, resolve, script)));
1271
+ } catch (own) {
1272
+ fail(ctx, own, {
1273
+ id: file,
1274
+ code: source
1275
+ });
1276
+ }
1277
+ }
1278
+ const line = error.loc && mappedLines(ctx, id)?.(error.loc.line, error.loc.column);
1279
+ if (line === void 0) fail(ctx, error, { id });
1280
+ const hint = error.hint === void 0 ? {} : { hint: error.hint };
1281
+ fail(ctx, new VicssError(error.code, error.message, {
1282
+ ...hint,
1283
+ loc: {
1284
+ file,
1285
+ line,
1286
+ column: 0
1287
+ }
1288
+ }), {
1289
+ id: file,
1290
+ code: source
1291
+ });
1292
+ }
1293
+ /**
1294
+ * Dev: a style written in a framework file's template works here, yet the
1295
+ * build's pre-scan reads only the file's scripts, and its safety net fails
1296
+ * the build (§5.4). It fails now, with the build's hint.
1297
+ */
1298
+ async function checkTemplate(ctx, s, id, result) {
1299
+ const file = fileOf(id);
1300
+ let code;
1301
+ try {
1302
+ code = readFileSync(file, "utf8");
1303
+ } catch {
1304
+ return;
1305
+ }
1306
+ const hash = hashCode(code);
1307
+ let scripts = scriptStyles.get(file);
1308
+ if (scripts?.hash !== hash) {
1309
+ const resolve = devResolver(ctx);
1310
+ let parts;
1311
+ try {
1312
+ parts = await Promise.all(extractScripts(file, code).map(({ code: text, ...script }) => compile(s.compile, file, text, resolve, script)));
1313
+ } catch {
1314
+ return;
1315
+ }
1316
+ scripts = {
1317
+ hash,
1318
+ styles: mergeStyles(parts)
1319
+ };
1320
+ scriptStyles.set(file, scripts);
1321
+ }
1322
+ const classes = new Set(scripts.styles.atoms.map((atom) => atom.className));
1323
+ const ids = new Set(scripts.styles.rules.map((rule) => rule.id));
1324
+ const rel = path.relative(s.root, file) || file;
1325
+ const own = result.own ?? {
1326
+ classes: result.atoms.map((a) => a.className),
1327
+ rules: result.rules.map((r) => r.id)
1328
+ };
1329
+ const atom = own.classes.find((name) => !classes.has(name));
1330
+ const rule = own.rules.find((ruleId) => !ids.has(ruleId));
1331
+ const [errorCode, what] = atom ? ["MISSING_ATOM", `the class \`${atom}\``] : rule ? ["MISSING_RULE", `the rule \`${rule}\``] : [];
1332
+ if (errorCode) ctx.error({
1333
+ code: errorCode,
1334
+ id,
1335
+ message: `vicss(${errorCode}): ${rel} uses ${what} outside its scripts: ${TEMPLATE_HINT}`
1336
+ });
1337
+ }
1338
+ /**
1339
+ * What a styled module imports: in dev, `base.css`, the installed
1340
+ * libraries' CSS and its own, in that order (a CSS module's imports run
1341
+ * before it, so a library's sub-layers would come before the statement
1342
+ * if `base.css` imported them); the sheet in an app build; nothing in a
1343
+ * library build, whose consumers print its manifest or link `vicss.css`.
1344
+ */
1345
+ function cssImports(s, id) {
1346
+ if (s.dev) return `\n${[
1347
+ BASE_SPECIFIER,
1348
+ ...s.loaded.libraries.map((_, i) => librarySpecifier(i)),
1349
+ moduleCssSpecifier(id)
1350
+ ].map((specifier) => `import "${specifier}";`).join("\n")}\n`;
1351
+ return s.library ? "" : `\nimport "${SHEET_SPECIFIER}";\n`;
1352
+ }
1353
+ /**
1354
+ * What a module of an installed library imports (PLAN §5.13): its classes
1355
+ * are the sheet's, so wherever the app renders its components, alone or
1356
+ * with no styles of its own, the sheet comes with them. In dev, `base.css`
1357
+ * and the libraries' CSS.
1358
+ */
1359
+ function libraryImports(s, library) {
1360
+ if (!s.dev) return `\nimport "${SHEET_SPECIFIER}";\n`;
1361
+ let code = `\n${[BASE_SPECIFIER, ...s.loaded.libraries.map((_, i) => librarySpecifier(i))].map((specifier) => `import "${specifier}";`).join("\n")}\n`;
1362
+ const bases = (library?.rules ?? []).filter((rule) => rule.kind === "base");
1363
+ if (bases.length > 0) {
1364
+ const shipped = (identity) => library.classes[identity] ?? identity;
1365
+ const atoms = Object.fromEntries(bases.map((rule) => [shipped(rule.id), Object.fromEntries((rule.atoms ?? []).map((atom) => [mergeIdOf(atom.className), shipped(atom.className)]))]));
1366
+ code += `import { __vxFlatBases } from "${RUNTIME_PACKAGE}";\nObject.assign(__vxFlatBases, ${JSON.stringify(atoms)});\n`;
1367
+ }
1368
+ return code;
1369
+ }
1370
+ const transformFilter = {
1371
+ id: SCRIPT_ID,
1372
+ code: sources
1373
+ };
1374
+ const frameworkFilter = {
1375
+ id: SFC_SCRIPT_ID,
1376
+ code: sources
1377
+ };
1378
+ /** Build: the installed libraries' directories, filled once they load. */
1379
+ const libraryDirs = [];
1380
+ const libraryFilter = { id: libraryDirs };
1381
+ const main = {
1382
+ name: "vicss",
1383
+ enforce: "pre",
1384
+ sharedDuringBuild: true,
1385
+ api: {
1386
+ options,
1387
+ /**
1388
+ * The app's whole stylesheet as one string, for a renderer outside the
1389
+ * browser (`@menapo/og` hands it to takumi, og PLAN D3): in a build,
1390
+ * what the scan prints, so it needs the hook's context; in dev, the
1391
+ * base and every compiled module's atoms, as the dev server serves them.
1392
+ */
1393
+ async stylesheet(ctx) {
1394
+ const s = ready();
1395
+ if (s.dev) return [
1396
+ devBaseCss(registry, s.print, s.loaded.libraries),
1397
+ ...s.loaded.libraries.map((library) => moduleCss(library, s.loaded.system, s.print.names)),
1398
+ ...registry.ids().map((id) => moduleCss(registry.get(id), s.loaded.system, s.print.names))
1399
+ ].join("\n");
1400
+ if (!ctx) throw new Error("vicss: api.stylesheet() needs a plugin hook's context in a build.");
1401
+ const printed = await sheetFor(ctx);
1402
+ return [printed?.css.tokens ?? "", printed?.css.sheet ?? ""].join("\n");
1403
+ },
1404
+ /**
1405
+ * The literal declarations (core/literals.ts) of the classes `code`, a
1406
+ * module as this plugin's transform left it, names: for a renderer
1407
+ * that cannot read custom properties (`@menapo/email` inlines them,
1408
+ * email PLAN D4). Keyed as the code writes each class, so a key written
1409
+ * back into the module is named with the module's own classes: a build
1410
+ * writes placeholders that `renderChunk` names, dev the class itself.
1411
+ * A build needs a hook's context, as `stylesheet()` does.
1412
+ */
1413
+ async literals(code, ctx) {
1414
+ const s = ready();
1415
+ const out = {};
1416
+ if (s.dev) {
1417
+ const table = literalsOf([...s.loaded.libraries.flatMap((library) => library.atoms), ...registry.ids().flatMap((id) => registry.get(id).atoms)], [...s.loaded.libraries.flatMap((library) => library.rules), ...registry.rules()], s.loaded.system);
1418
+ const byName = /* @__PURE__ */ new Map();
1419
+ for (const [identity, list] of table) byName.set(s.print.names.name(identity), list);
1420
+ for (const name of classesIn(code, new Set(byName.keys()))) out[name] = byName.get(name);
1421
+ return out;
1422
+ }
1423
+ if (!ctx) throw new Error("vicss: api.literals() needs a plugin hook's context in a build.");
1424
+ const sheet = await sheetFor(ctx);
1425
+ if (!sheet) return out;
1426
+ let table = literalTables.get(sheet.scan);
1427
+ if (!table) {
1428
+ const all = withLibraries(sheet.scan, s.loaded.libraries, s.loaded.system);
1429
+ table = literalsOf(all.atoms, all.rules, s.loaded.system);
1430
+ literalTables.set(sheet.scan, table);
1431
+ }
1432
+ for (const identity of placeholdersIn(code)) {
1433
+ const list = table.get(identity);
1434
+ if (list) out[placeholder(identity)] = list;
1435
+ }
1436
+ return out;
1437
+ }
1438
+ },
1439
+ async configResolved(config) {
1440
+ if (state && !state.dev && config.command === "build" && !session.started) {
1441
+ session.configs++;
1442
+ return;
1443
+ }
1444
+ if (layers) outerLayers(layers);
1445
+ const dev = config.command === "serve";
1446
+ if (dev && config.experimental.bundledDev) config.logger.warn("vicss: experimental.bundledDev is not supported yet; CSS updates may arrive late or not at all");
1447
+ const html = hasHtmlEntry(config);
1448
+ const split = config.environments.client?.build.cssCodeSplit !== false;
1449
+ if (!dev && options.tokensFile === true && !html) config.logger.warn("vicss: tokensFile: true needs an HTML entry in the client build to link the file; the tokens stay at the start of the sheet");
1450
+ else if (!dev && options.tokensFile === true && !split) config.logger.warn("vicss: tokensFile: true needs build.cssCodeSplit; with one CSS file for the app, the tokens stay at the start of the sheet");
1451
+ const root = normalizePath(config.root);
1452
+ spawned?.close();
1453
+ spawned = void 0;
1454
+ if (!dev && !existsSync(path.join(config.cacheDir, "vicss", "scan.json"))) {
1455
+ const files = await listFiles(root);
1456
+ const estimate = files.length > 200 ? estimateStyled(files, [RUNTIME_PACKAGE]) : 0;
1457
+ if (estimate > 200) spawned = CompilePool.spawn(poolSize(estimate));
1458
+ }
1459
+ const critical = criticalSettings(options.critical);
1460
+ if (critical && !dev && config.build?.lib) config.logger.warn("vicss: critical needs pages, and a library build has none; it does nothing here");
1461
+ else if (critical && !dev && !html) config.logger.info("vicss: critical: this build emits no HTML page; once your framework has written its pages, run `vicss critical <outDir>`");
1462
+ if (options.framework !== void 0 && !FRAMEWORKS.includes(options.framework)) throw new Error(`vicss: framework must be one of ${FRAMEWORKS.join(", ")}; got ${JSON.stringify(options.framework)}`);
1463
+ const s = {
1464
+ config,
1465
+ root,
1466
+ ids: cssIds(root),
1467
+ dev,
1468
+ devSource: dev && !isTestRunner(config),
1469
+ loaded: void 0,
1470
+ compile: void 0,
1471
+ print: void 0,
1472
+ tokensFile: options.tokensFile !== false && html && split,
1473
+ library: !dev && Boolean(config.build?.lib),
1474
+ configHash: "",
1475
+ diskCache: false,
1476
+ frameworks: {
1477
+ scripts: options.framework ?? detectFramework(root) ?? "vanilla",
1478
+ jsxImportSource: configuredJsxSource(config, root)
1479
+ },
1480
+ pkg: ownPackageName(root)
1481
+ };
1482
+ let loaded;
1483
+ try {
1484
+ loaded = checkLibraries(s, await loadSystem(root, options.config, (m) => config.logger.info(m)));
1485
+ } catch (error) {
1486
+ throw loadFailure(error, root, options.config);
1487
+ }
1488
+ setSystem(s, loaded);
1489
+ for (const filter of [transformFilter, frameworkFilter]) if (s.dev) delete filter.code;
1490
+ else filter.code = sources;
1491
+ if (s.dev) delete libraryFilter.id;
1492
+ else libraryFilter.id = libraryDirs;
1493
+ if (state) {
1494
+ registry.clear();
1495
+ cache.clear();
1496
+ diskCache = void 0;
1497
+ scans.clear();
1498
+ kept.clear();
1499
+ transformed.clear();
1500
+ scriptStyles.clear();
1501
+ changes.clear();
1502
+ resolutions.clear();
1503
+ }
1504
+ session = newSession();
1505
+ state = s;
1506
+ },
1507
+ configureServer(dev) {
1508
+ server = dev;
1509
+ const s = ready();
1510
+ let release;
1511
+ closing = new Promise((resolve) => release = resolve);
1512
+ const client = dev.environments.client;
1513
+ if (client) {
1514
+ const close = client.close.bind(client);
1515
+ client.close = () => {
1516
+ release();
1517
+ return close();
1518
+ };
1519
+ }
1520
+ const forget = () => resolutions.clear();
1521
+ for (const event of [
1522
+ "add",
1523
+ "unlink",
1524
+ "addDir",
1525
+ "unlinkDir"
1526
+ ]) dev.watcher.on(event, forget);
1527
+ dev.watcher.on("change", (file) => {
1528
+ if (/(?:^|[/\\])(?:package|[jt]sconfig[^/\\]*)\.json$/.test(file)) forget();
1529
+ });
1530
+ const candidates = options.config ? [] : CONFIG_FILES.map((name) => normalizePath(path.join(s.root, name)));
1531
+ dev.watcher.add([...s.loaded.watch, ...candidates]);
1532
+ let reloading = Promise.resolve();
1533
+ let queued = false;
1534
+ const onChange = (file) => {
1535
+ const id = normalizePath(file);
1536
+ const watched = ready().loaded.watch.includes(id) || candidates.includes(id) || failedReads.has(id);
1537
+ if (queued || !watched) return;
1538
+ queued = true;
1539
+ reloading = reloading.then(() => {
1540
+ queued = false;
1541
+ return reloadConfig(dev).catch((error) => {
1542
+ ready().config.logger.error(`vicss: ${String(error)}`, { timestamp: true });
1543
+ });
1544
+ });
1545
+ };
1546
+ dev.watcher.on("change", onChange);
1547
+ dev.watcher.on("add", onChange);
1548
+ dev.watcher.on("unlink", onChange);
1549
+ },
1550
+ async buildStart() {
1551
+ const s = ready();
1552
+ if (s.dev) return;
1553
+ session.started = true;
1554
+ if (this.meta.watchMode) {
1555
+ if (configChanged) await reloadBuildConfig(this, s);
1556
+ for (const file of [...s.loaded.watch, ...failedReads]) this.addWatchFile(file);
1557
+ }
1558
+ buildResolutions.delete(this.environment);
1559
+ const run = startScan(this);
1560
+ if (this.meta.watchMode) {
1561
+ await run.result.catch(() => {});
1562
+ const sheet = await sheetFor(this);
1563
+ for (const file of sheet?.scan.files ?? []) this.addWatchFile(file);
1564
+ }
1565
+ if (s.tokensFile && this.environment.config.consumer === "client") {
1566
+ this.emitFile({
1567
+ type: "chunk",
1568
+ id: s.ids.tokens,
1569
+ name: TOKENS_CHUNK_NAME
1570
+ });
1571
+ tokenAssets.set(this.environment, { config: this.environment.config });
1572
+ }
1573
+ },
1574
+ watchChange(id) {
1575
+ scans.clear();
1576
+ kept.clear();
1577
+ const file = normalizePath(id);
1578
+ if (state && !state.dev && (state.loaded.watch.includes(file) || failedReads.has(file))) configChanged = true;
1579
+ },
1580
+ resolveId: {
1581
+ filter: { id: CSS_ID },
1582
+ handler(source, importer) {
1583
+ const s = ready();
1584
+ const [file, query] = splitQuery(source);
1585
+ if (file === "virtual:vicss/base.css" || file === "/@vicss/base.css" || file === s.ids.base) return s.dev ? s.ids.base + query : null;
1586
+ if (file === "virtual:vicss/sheet.css" || file === "/@vicss/sheet.css" || file === s.ids.sheet) return s.dev ? null : s.ids.sheet + query;
1587
+ if (file === "/@vicss/tokens.css" || file === s.ids.tokens) return s.dev || !s.tokensFile ? null : s.ids.tokens + query;
1588
+ const library = /(?:^virtual:vicss\/lib\/|\/@vicss\/lib\/)(\d+)\.css$/.exec(file);
1589
+ if (library) return s.dev ? `${s.ids.libraries}${library[1]}.css${query}` : null;
1590
+ if (!s.dev || !file.endsWith(".vicss.css")) return null;
1591
+ return moduleCssId(s.root, file, importer) + query;
1592
+ }
1593
+ },
1594
+ load: {
1595
+ filter: { id: CSS_ID },
1596
+ async handler(id) {
1597
+ const s = ready();
1598
+ const [file] = splitQuery(id);
1599
+ if (file === s.ids.sheet && !s.dev) return (await sheetFor(this))?.css.sheet ?? "";
1600
+ if (file === s.ids.tokens && !s.dev) return (await sheetFor(this))?.css.tokens ?? "";
1601
+ if (!s.dev) return null;
1602
+ if (file.startsWith(s.ids.libraries)) {
1603
+ const library = s.loaded.libraries[Number(file.slice(s.ids.libraries.length, -4))];
1604
+ return library ? moduleCss(library, s.loaded.system, s.print.names) : "";
1605
+ }
1606
+ if (file === s.ids.base) {
1607
+ const env = this.environment;
1608
+ if (env.mode === "dev" && env.config.consumer === "client") await Promise.race([env.waitForRequestsIdle(id), closing]);
1609
+ await rebuilt;
1610
+ try {
1611
+ return devBaseCss(registry, s.print, s.loaded.libraries);
1612
+ } catch (error) {
1613
+ return fail(this, error, { id });
1614
+ }
1615
+ }
1616
+ if (!file.endsWith(".vicss.css")) return null;
1617
+ const source = file.slice(0, -10);
1618
+ let styles = registry.ofFile(source);
1619
+ if (!styles) {
1620
+ if (!(SCRIPT_ID.test(source) || isSfc(source)) || source.includes("/node_modules/") || !isFileLoadingAllowed(s.config, source)) return "";
1621
+ let code;
1622
+ try {
1623
+ code = await readFile(source, "utf8");
1624
+ } catch {
1625
+ return "";
1626
+ }
1627
+ let result;
1628
+ try {
1629
+ const resolve = devResolver(this);
1630
+ result = isSfc(source) ? mergeStyles(await Promise.all(extractScripts(source, code).map(({ code: script, ...options }) => compile(s.compile, source, script, resolve, options)))) : await compile(s.compile, source, code, resolve);
1631
+ } catch (error) {
1632
+ fail(this, error, {
1633
+ id: source,
1634
+ code
1635
+ });
1636
+ }
1637
+ for (const dep of result.dependencies) this.addWatchFile(dep);
1638
+ if (register(source, result)) baseChanged();
1639
+ styles = result;
1640
+ }
1641
+ this.addWatchFile(source);
1642
+ return moduleCss(styles, s.loaded.system, s.print.names);
1643
+ }
1644
+ },
1645
+ transform: {
1646
+ order: "pre",
1647
+ filter: transformFilter,
1648
+ handler(code, id) {
1649
+ if (isSfc(id)) return null;
1650
+ return transformStyled(this, code, id);
1651
+ }
1652
+ },
1653
+ async hotUpdate(ctx) {
1654
+ const s = ready();
1655
+ const file = normalizePath(ctx.file);
1656
+ if (!s.dev || s.loaded.watch.includes(file) || DECLARATION_ID.test(file)) return;
1657
+ const key = `${ctx.timestamp}:${file}`;
1658
+ let change = changes.get(key);
1659
+ if (!change) {
1660
+ for (const [k, c] of changes) if (c.at < ctx.timestamp - 1e4) changes.delete(k);
1661
+ const read = () => readFileSync(file, "utf8") || ctx.read();
1662
+ const resolve = memoize(resolutions, this.environment.name, envResolver(s, this.environment));
1663
+ const done = applyChange(file, ctx.type, read, resolve);
1664
+ changes.set(key, change = {
1665
+ at: ctx.timestamp,
1666
+ done
1667
+ });
1668
+ }
1669
+ const { styled, dependants, base } = await change.done;
1670
+ if (styled.length === 0 && dependants.length === 0 && !base) return;
1671
+ const graph = this.environment.moduleGraph;
1672
+ const updates = new Set(ctx.modules);
1673
+ const files = [
1674
+ ...dependants.map(fileOf),
1675
+ ...styled.map((id) => fileOf(id) + MODULE_SUFFIX),
1676
+ ...base ? [s.ids.base] : []
1677
+ ];
1678
+ for (const updated of files) for (const mod of graph.getModulesByFile(updated) ?? []) {
1679
+ graph.invalidateModule(mod, /* @__PURE__ */ new Set(), ctx.timestamp, true);
1680
+ updates.add(mod);
1681
+ }
1682
+ return [...updates];
1683
+ },
1684
+ /**
1685
+ * Compiled code names classes by placeholder (core/names.ts): the
1686
+ * build's scan knows every class by now, the sheet's load having waited
1687
+ * for it, so each chunk gets the names the sheet prints (PLAN D31).
1688
+ */
1689
+ async renderChunk(code, chunk) {
1690
+ if (state?.dev !== false || !hasPlaceholders(code)) return null;
1691
+ const sheet = await sheetFor(this);
1692
+ if (!sheet) return null;
1693
+ const edits = new SourceEdits(code);
1694
+ for (const { start, end, text } of renameClasses(code, (identity) => sheet.names.name(identity))) edits.overwrite(start, end, text);
1695
+ const file = chunk.fileName;
1696
+ return {
1697
+ code: edits.toString(),
1698
+ map: this.environment.config.build.sourcemap ? edits.generateMap({
1699
+ source: file,
1700
+ file,
1701
+ hires: true
1702
+ }) : null
1703
+ };
1704
+ },
1705
+ async generateBundle(_options, bundle) {
1706
+ if (state?.dev !== false) return;
1707
+ if (state.library && this.environment.config.build?.lib) {
1708
+ const s = state;
1709
+ const sheet = await sheetFor(this);
1710
+ if (!sheet) return;
1711
+ const result = sheet.scan;
1712
+ const classes = Object.fromEntries(printed(result).identities.map((identity) => [identity, sheet.names.name(identity)]));
1713
+ const manifest = createManifest(result, s.loaded.system, s.pkg, classes);
1714
+ this.emitFile({
1715
+ type: "asset",
1716
+ fileName: MANIFEST_FILE,
1717
+ source: `${JSON.stringify(manifest, null, 2)}\n`
1718
+ });
1719
+ this.emitFile({
1720
+ type: "asset",
1721
+ fileName: LIBRARY_SHEET_FILE,
1722
+ source: buildCss(result, {
1723
+ ...s.print,
1724
+ names: sheet.names
1725
+ }, false).sheet
1726
+ });
1727
+ }
1728
+ const names = [];
1729
+ const tokens = tokenAssets.get(this.environment);
1730
+ for (const chunk of Object.values(bundle)) {
1731
+ if (chunk.type !== "chunk") continue;
1732
+ const css = [...chunk.viteMetadata?.importedCss ?? []];
1733
+ if (chunk.moduleIds.includes(state.ids.sheet)) names.push(...css);
1734
+ if (tokens && chunk.facadeModuleId === state.ids.tokens) tokens.fileName = css[0];
1735
+ }
1736
+ sheetFiles.set(this.environment, names);
1737
+ },
1738
+ writeBundle(_options, bundle) {
1739
+ const names = [...sheetFiles.get(this.environment) ?? []];
1740
+ const tokens = tokenAssets.get(this.environment)?.fileName;
1741
+ if (!session.scan || names.length === 0) return;
1742
+ if (tokens) names.push(tokens);
1743
+ const files = names.flatMap((name) => {
1744
+ const asset = bundle[name];
1745
+ return asset?.type === "asset" ? [[name, asset.source]] : [];
1746
+ });
1747
+ if (files.length === names.length) summarize(session.scan, files);
1748
+ },
1749
+ async buildEnd(error) {
1750
+ if (error) session.failed = true;
1751
+ if (error || state?.dev !== false) return;
1752
+ session.failed = true;
1753
+ const sheet = await sheetFor(this);
1754
+ if (!sheet) return;
1755
+ session.failed = false;
1756
+ session.scan = sheet.scan;
1757
+ },
1758
+ async closeBundle() {
1759
+ const s = state;
1760
+ if (!s || s.dev || ++session.closed < Math.max(1, session.configs - 1)) return;
1761
+ await Promise.all(cacheWrites.splice(0));
1762
+ kept.clear();
1763
+ if (s.diskCache) {
1764
+ cache.clear();
1765
+ diskCache = void 0;
1766
+ }
1767
+ s.compile = {
1768
+ ...s.compile,
1769
+ parsed: createParseCache()
1770
+ };
1771
+ if (session.scan && !session.failed) summarize(session.scan);
1772
+ }
1773
+ };
1774
+ /**
1775
+ * `.vue`, `.svelte` and `.astro` modules once their framework's plugin has
1776
+ * compiled them to JS (PLAN §5.4): the same compile, CSS and registry as
1777
+ * the `pre` transform. The pre-scan read the files' scripts; a style call
1778
+ * in a template reaches this transform only, and the safety net says so.
1779
+ */
1780
+ /**
1781
+ * The modules of installed libraries: they bring the CSS their classes
1782
+ * need, and name the app's custom properties where their JS names the
1783
+ * library's own (`token()`, runtime values) under another prefix.
1784
+ */
1785
+ const libraries = {
1786
+ name: "vicss:libraries",
1787
+ sharedDuringBuild: true,
1788
+ config(config) {
1789
+ const root = path.resolve(config.root ?? process.cwd());
1790
+ const names = [...new Set(findLibraries(normalizePath(root)).map((library) => library.name))];
1791
+ if (names.length === 0) return void 0;
1792
+ return {
1793
+ optimizeDeps: { exclude: names },
1794
+ ssr: { noExternal: names }
1795
+ };
1796
+ },
1797
+ transform: {
1798
+ filter: libraryFilter,
1799
+ handler(code, id) {
1800
+ const s = ready();
1801
+ const file = fileOf(id);
1802
+ const library = SCRIPT_ID.test(file) ? libraryOf(s.loaded.libraries, file) : void 0;
1803
+ if (s.library || !library) return null;
1804
+ const renamed = library.renames && renameCustomProperties(code, file, library.renames);
1805
+ if (!renamed) return {
1806
+ code: code + libraryImports(s, library),
1807
+ map: null
1808
+ };
1809
+ return {
1810
+ code: renamed.code + libraryImports(s, library),
1811
+ map: renamed.map
1812
+ };
1813
+ }
1814
+ },
1815
+ transformIndexHtml: {
1816
+ order: "pre",
1817
+ handler() {
1818
+ const s = ready();
1819
+ if (!s.dev || s.loaded.libraries.length === 0) return void 0;
1820
+ return [{
1821
+ tag: "script",
1822
+ attrs: { type: "module" },
1823
+ children: libraryImports(s).trim(),
1824
+ injectTo: "head-prepend"
1825
+ }];
1826
+ }
1827
+ }
1828
+ };
1829
+ const frameworks = {
1830
+ name: "vicss:frameworks",
1831
+ enforce: "post",
1832
+ sharedDuringBuild: true,
1833
+ transform: {
1834
+ filter: frameworkFilter,
1835
+ handler(code, id) {
1836
+ return transformStyled(this, code, id);
1837
+ }
1838
+ }
1839
+ };
1840
+ const html = {
1841
+ name: "vicss:tokens-file",
1842
+ apply: "build",
1843
+ sharedDuringBuild: true,
1844
+ transformIndexHtml: {
1845
+ order: "post",
1846
+ handler(_html, ctx) {
1847
+ for (const asset of tokenAssets.values()) if (asset.fileName && ctx.bundle?.[asset.fileName]) {
1848
+ const page = normalizePath(path.relative(asset.config.root, ctx.filename));
1849
+ return [tokensLink(asset.config, asset.fileName, page)];
1850
+ }
1851
+ }
1852
+ },
1853
+ buildEnd(error) {
1854
+ if (error) tokenAssets.delete(this.environment);
1855
+ },
1856
+ closeBundle() {
1857
+ tokenAssets.delete(this.environment);
1858
+ }
1859
+ };
1860
+ /**
1861
+ * Build: what critical CSS did to each page, by its file, until the
1862
+ * bundle that emits the page claims it. The HTML hook has no
1863
+ * environment, and Rolldown hands each hook its own bundle object.
1864
+ */
1865
+ const criticalPages = /* @__PURE__ */ new Map();
1866
+ /** Build: each bundle's CSS files, parsed once for all of its pages. */
1867
+ const criticalSheets = /* @__PURE__ */ new WeakMap();
1868
+ const criticalCss = {
1869
+ name: "vicss:critical",
1870
+ apply: "build",
1871
+ enforce: "post",
1872
+ sharedDuringBuild: true,
1873
+ transformIndexHtml: {
1874
+ order: "post",
1875
+ handler(html, ctx) {
1876
+ const settings = criticalSettings(options.critical);
1877
+ const bundle = ctx.bundle;
1878
+ if (!settings || !bundle || state?.dev !== false) return void 0;
1879
+ let output = criticalSheets.get(bundle);
1880
+ if (!output) {
1881
+ const assets = Object.values(bundle).flatMap((file) => file.type === "asset" && file.fileName.endsWith(".css") ? [file] : []);
1882
+ const sources = new Map(assets.map((asset) => [asset.fileName, asset.source]));
1883
+ const { publicDir } = state.config;
1884
+ const own = publicDir && existsSync(publicDir) ? publicStylesheets(publicDir) : [];
1885
+ const names = session.scan && printed(session.scan).names;
1886
+ const sheets = new SheetCache((file) => {
1887
+ const source = sources.get(file);
1888
+ if (source !== void 0) return typeof source === "string" ? source : Buffer.from(source).toString();
1889
+ return own.includes(file) ? readFileSync(path.join(publicDir, file), "utf8") : void 0;
1890
+ }, names ? (cls) => names.relates(cls) : void 0);
1891
+ output = {
1892
+ files: [...sources.keys(), ...own],
1893
+ sheets
1894
+ };
1895
+ criticalSheets.set(bundle, output);
1896
+ }
1897
+ const result = criticalPage({
1898
+ page: ctx.path.replace(/^\//, ""),
1899
+ html,
1900
+ ...output,
1901
+ requireClasses: true
1902
+ });
1903
+ criticalPages.set(result.page, result);
1904
+ return result.html;
1905
+ }
1906
+ },
1907
+ generateBundle(_options, bundle) {
1908
+ const settings = criticalSettings(options.critical);
1909
+ const pages = [...criticalPages.values()].filter((page) => bundle[page.page]?.type === "asset");
1910
+ if (!settings || pages.length === 0) return;
1911
+ for (const page of pages) criticalPages.delete(page.page);
1912
+ const inlined = pages.some((page) => page.html !== void 0);
1913
+ if (settings.hashes !== false && inlined) this.emitFile({
1914
+ type: "asset",
1915
+ fileName: settings.hashes,
1916
+ source: hashesJson(pages, settings.budget)
1917
+ });
1918
+ const { logger } = this.environment.config;
1919
+ const result = report(pages, settings.budget, inlined && settings.hashes ? settings.hashes : void 0);
1920
+ logger.info(result.summary);
1921
+ for (const warning of result.warnings) logger.warn(`vicss: ${warning}`);
1922
+ for (const page of pages) if (page.skipped !== void 0 && !page.warning) logger.info(`vicss: ${page.page}: left as it was: ${page.skipped}`);
1923
+ }
1924
+ };
1925
+ /** Where a check names the module: relative to the root, as `data-vx-src` does. */
1926
+ const sourceName = (id) => {
1927
+ const file = id.replace(/[?#].*$/, "");
1928
+ const root = ready().root.replace(/\/+$/, "");
1929
+ return file.startsWith(`${root}/`) ? file.slice(root.length + 1) : file;
1930
+ };
1931
+ return [
1932
+ main,
1933
+ {
1934
+ name: "vicss:style-checks",
1935
+ enforce: "pre",
1936
+ apply: "serve",
1937
+ sharedDuringBuild: true,
1938
+ transform: {
1939
+ order: "pre",
1940
+ filter: {
1941
+ id: SCRIPT_ID,
1942
+ code: "style="
1943
+ },
1944
+ handler(code, id) {
1945
+ if (id.includes("/node_modules/") || DECLARATION_ID.test(id)) return null;
1946
+ return checkStyles(code, id, sourceName(id));
1947
+ }
1948
+ }
1949
+ },
1950
+ libraries,
1951
+ frameworks,
1952
+ {
1953
+ name: "vicss:attributes",
1954
+ enforce: "post",
1955
+ sharedDuringBuild: true,
1956
+ transform: {
1957
+ filter: {
1958
+ id: SFC_SCRIPT_ID,
1959
+ code: COMPONENT_CALLS
1960
+ },
1961
+ handler(code, id) {
1962
+ if (id.includes("/node_modules/")) return null;
1963
+ return rewriteComponent(code, id, {
1964
+ dev: ready().dev,
1965
+ src: sourceName(id)
1966
+ });
1967
+ }
1968
+ }
1969
+ },
1970
+ html,
1971
+ criticalCss
1972
+ ];
1973
+ }
1974
+ /** The `.css` files under the public directory, relative to it, as Vite copies them to the output. */
1975
+ function publicStylesheets(dir) {
1976
+ return readdirSync(dir, {
1977
+ recursive: true,
1978
+ withFileTypes: true
1979
+ }).flatMap((entry) => entry.isFile() && entry.name.endsWith(".css") ? [normalizePath(path.relative(dir, path.join(entry.parentPath, entry.name)))] : []);
1980
+ }
1981
+ /**
1982
+ * What the scan's worker threads build their compiler from: the merged config
1983
+ * as data, or, when it holds functions (utilities), the file that defines
1984
+ * it. A config object with functions cannot cross to a thread: no workers.
1985
+ */
1986
+ function workerSetup(s) {
1987
+ const { system, file } = s.loaded;
1988
+ const common = {
1989
+ root: s.root,
1990
+ hash: system.hash,
1991
+ sourceMap: s.compile.sourceMap,
1992
+ dev: s.compile.dev === true,
1993
+ placeholders: s.compile.placeholders === true,
1994
+ package: s.pkg,
1995
+ frameworks: s.frameworks
1996
+ };
1997
+ if (!hasFunction(system.config)) return {
1998
+ ...common,
1999
+ config: { data: system.config }
2000
+ };
2001
+ return file ? {
2002
+ ...common,
2003
+ config: {
2004
+ file,
2005
+ shape: configShape(system.config)
2006
+ }
2007
+ } : void 0;
2008
+ }
2009
+ /**
2010
+ * An error thrown while the config loads (an invalid config, a library the
2011
+ * design system does not cover), worded as `fail` words a module's:
2012
+ * `vicss(CODE): message` with the hint below it. Vite prints a startup
2013
+ * error's message and stack, and the JS API's callers read the message; a
2014
+ * VicssError keeps its hint apart. A config error names the config file.
2015
+ */
2016
+ function loadFailure(error, root, option) {
2017
+ if (!(error instanceof VicssError)) return error;
2018
+ const { code, hint } = error;
2019
+ const failure = /* @__PURE__ */ new Error(`vicss(${code}): ${error.message}${hint ? `\n${hint}` : ""}`);
2020
+ const file = typeof option === "string" ? normalizePath(path.resolve(root, option)) : option === void 0 ? CONFIG_FILES.map((name) => normalizePath(path.join(root, name))).find(existsSync) : void 0;
2021
+ return Object.assign(failure, {
2022
+ code,
2023
+ plugin: "vicss",
2024
+ ...code === "CONFIG" && file ? { id: file } : {}
2025
+ });
2026
+ }
2027
+ /**
2028
+ * What a package manager writes when it installs: lockfiles, and the state
2029
+ * files some keep in `node_modules` (Vite's dependency optimizer reads the
2030
+ * same). The nearest directory above `root` that has any, as the workspace
2031
+ * root of a monorepo does.
2032
+ */
2033
+ const LOCKFILES = [
2034
+ "pnpm-lock.yaml",
2035
+ "package-lock.json",
2036
+ "npm-shrinkwrap.json",
2037
+ "yarn.lock",
2038
+ "bun.lock",
2039
+ "bun.lockb",
2040
+ "node_modules/.pnpm/lock.yaml",
2041
+ "node_modules/.package-lock.json",
2042
+ "node_modules/.yarn-state.yml"
2043
+ ];
2044
+ function lockfiles(root) {
2045
+ for (let dir = root;; dir = path.dirname(dir)) {
2046
+ const found = LOCKFILES.map((name) => normalizePath(path.join(dir, name))).filter(existsSync);
2047
+ if (found.length > 0 || path.dirname(dir) === dir) return found;
2048
+ }
2049
+ }
2050
+ /**
2051
+ * The lines of a framework file that its compiled output came from, read
2052
+ * from the map Vite combined for the module so far; undefined without one.
2053
+ */
2054
+ function mappedLines(ctx, id) {
2055
+ const file = fileOf(id);
2056
+ let map;
2057
+ try {
2058
+ map = ctx.getCombinedSourcemap();
2059
+ } catch {
2060
+ return;
2061
+ }
2062
+ const only = map.sources.length === 1;
2063
+ return originalLines(map, (source) => only || normalizePath(path.resolve(path.dirname(file), source)) === file);
2064
+ }
2065
+ /**
2066
+ * Whether a build ships readable names: `classNames.readable`, else
2067
+ * `VICSS_READABLE=1` in the environment, which debugs a production build,
2068
+ * and runs a test suite's builds, without editing the config.
2069
+ */
2070
+ function readableBuild(options) {
2071
+ return options.readable ?? process.env.VICSS_READABLE === "1";
2072
+ }
2073
+ /**
2074
+ * What a build names (core/names.ts): every atom, theme, flattened base and
2075
+ * marker of the sheet, and every class compiled code names (`names`, from
2076
+ * the scan): markers applied without a style, palette prefixes.
2077
+ */
2078
+ function identitiesOf(styles, names) {
2079
+ const out = new Set(names);
2080
+ for (const atom of styles.atoms) {
2081
+ out.add(atom.className);
2082
+ for (const condition of atom.chain) {
2083
+ const marker = /^_vx:m:([gp]):([0-9a-z]{6}):/.exec(condition);
2084
+ if (marker) out.add(`vx-${marker[1]}${marker[2]}_m`);
2085
+ }
2086
+ }
2087
+ for (const rule of styles.rules) if (rule.kind !== "keyframes") out.add(rule.id);
2088
+ return [...out].sort();
2089
+ }
2090
+ /** One build: the configs it resolved, the environments that closed, its scan. */
2091
+ function newSession() {
2092
+ return {
2093
+ started: false,
2094
+ configs: 1,
2095
+ closed: 0,
2096
+ failed: false,
2097
+ scan: void 0
2098
+ };
2099
+ }
2100
+ /**
2101
+ * Resolution for the compiler, through the environment's plugins. Constants
2102
+ * become class names the server must render as the client does, so an
2103
+ * installed package resolves to the file the client reads, in every
2104
+ * environment and in the scan whichever environment starts it; one a server
2105
+ * build externalizes is still read at build time.
2106
+ */
2107
+ function constantsResolver(s, env, resolve) {
2108
+ return async (source, importer) => {
2109
+ const resolved = await resolve(source, importer);
2110
+ const server = env.config.consumer === "server";
2111
+ if (!resolved || !resolved.external && !(server && resolved.id.includes("/node_modules/"))) return resolved;
2112
+ const { conditions, mainFields } = s.config.environments.client.resolve;
2113
+ s.clientResolve ??= createIdResolver(s.config, {
2114
+ conditions,
2115
+ mainFields,
2116
+ external: [],
2117
+ noExternal: true
2118
+ });
2119
+ const id = await s.clientResolve(env, source, importer);
2120
+ if (id) return { id };
2121
+ if (!resolved.external) return resolved;
2122
+ return path.isAbsolute(resolved.id) ? { id: resolved.id } : null;
2123
+ };
2124
+ }
2125
+ function contextResolver(s, ctx) {
2126
+ return constantsResolver(s, ctx.environment, (source, importer) => ctx.resolve(source, importer, { skipSelf: true }));
2127
+ }
2128
+ /** Resolution outside a plugin hook, through the environment's plugins. */
2129
+ function envResolver(s, env) {
2130
+ return constantsResolver(s, env, (source, importer) => env.pluginContainer.resolveId(source, importer));
2131
+ }
2132
+ /**
2133
+ * Resolutions by importer directory and request: a request resolves the same
2134
+ * from every file of a directory, and most files import the same
2135
+ * design-system modules, so each is resolved once.
2136
+ */
2137
+ function memoize(resolved, prefix, resolve) {
2138
+ return (source, importer) => {
2139
+ const key = `${prefix}\0${path.dirname(importer)}\0${source}`;
2140
+ let found = resolved.get(key);
2141
+ if (!found) {
2142
+ resolved.set(key, found = resolve(source, importer));
2143
+ found.catch(() => resolved.delete(key));
2144
+ }
2145
+ return found;
2146
+ };
2147
+ }
2148
+ /** `run` over `items`, `BATCH` at a time, results in order. */
2149
+ async function inBatches(items, run) {
2150
+ const out = [];
2151
+ for (let i = 0; i < items.length; i += BATCH) out.push(...await Promise.all(items.slice(i, i + BATCH).map(run)));
2152
+ return out;
2153
+ }
2154
+ /** The styles of several modules (a framework file's scripts) as one. */
2155
+ function mergeStyles(parts) {
2156
+ return {
2157
+ atoms: [...new Map(parts.flatMap((part) => part.atoms).map((a) => [a.className, a])).values()].sort((a, b) => a.className < b.className ? -1 : 1),
2158
+ tokens: parts.flatMap((part) => part.tokens),
2159
+ rules: parts.flatMap((part) => part.rules),
2160
+ dependencies: [...new Set(parts.flatMap((part) => part.dependencies))]
2161
+ };
2162
+ }
2163
+ /** Whether a module's dev CSS prints alike: the same atoms and flattened bases. */
2164
+ const sameCss = (a, b) => {
2165
+ const bases = (styles) => styles.rules.filter((rule) => rule.kind === "base").map((rule) => rule.id);
2166
+ const [x, y] = [bases(a), bases(b)];
2167
+ return a.atoms.length === b.atoms.length && a.atoms.every((atom, i) => atom.className === b.atoms[i].className) && x.length === y.length && x.every((id, i) => id === y[i]);
2168
+ };
2169
+ /** Why a style in a framework file's template fails (PLAN §5.4). */
2170
+ const TEMPLATE_HINT = "styles written in a component's template are not supported, since the pre-scan reads only the file's scripts. Move the style into the <script> (or the frontmatter): `const card = css({ … })`, then use `card` in the template.";
2171
+ /** Matches no module id: a filter with no patterns would match every one. */
2172
+ const NOTHING = /^\0vicss:no-library$/;
2173
+ function hasFunction(value) {
2174
+ if (typeof value === "function") return true;
2175
+ if (typeof value !== "object" || value === null) return false;
2176
+ return Object.values(value).some(hasFunction);
2177
+ }
2178
+ /**
2179
+ * `./X.tsx.vicss.css` from its importer, `/src/X.tsx.vicss.css` from the
2180
+ * root (the dev server's URL), or an absolute path (`/@fs/…` or `/@id/…`
2181
+ * outside the root).
2182
+ */
2183
+ function moduleCssId(root, file, importer) {
2184
+ if (file.startsWith("./") || file.startsWith("../")) {
2185
+ const from = importer ? path.dirname(splitQuery(importer)[0]) : root;
2186
+ return normalizePath(path.resolve(from, file));
2187
+ }
2188
+ if (file.startsWith("/@fs/")) return file.slice(4);
2189
+ if (file.startsWith(`${root}/`)) return file;
2190
+ const source = file.slice(0, -10);
2191
+ return existsSync(source) && !existsSync(root + source) ? file : root + file;
2192
+ }
2193
+ /** The safety net: a module compiled outside the scan needs atoms or rules the sheet lacks. */
2194
+ function checkSheet(ctx, sheet, tokenKeys, result, id, root) {
2195
+ const clean = fileOf(id);
2196
+ const file = path.relative(root, clean) || clean;
2197
+ const why = !sheet.files.includes(clean) ? "the file is outside `include` in vicss.config.ts, so the pre-scan never compiled it. Add it to `include` (or remove it from `exclude`)." : isSfc(clean) ? TEMPLATE_HINT : `the pre-scan compiled this file to other atoms: one of its imports resolves differently in the ${ctx.environment.name} environment, or the file changed during the build.`;
2198
+ const atom = result.atoms.find((a) => !sheet.classes.has(a.className));
2199
+ if (atom) ctx.error({
2200
+ code: "MISSING_ATOM",
2201
+ id,
2202
+ message: `vicss(MISSING_ATOM): ${file} uses the class \`${atom.className}\`, which the sheet lacks: ${why}`
2203
+ });
2204
+ const rule = result.rules.find((r) => !sheet.ruleIds.has(r.id));
2205
+ if (rule) {
2206
+ const kind = rule.kind === "base" ? "flattened recipe base" : rule.kind;
2207
+ ctx.error({
2208
+ code: "MISSING_RULE",
2209
+ id,
2210
+ message: `vicss(MISSING_RULE): ${file} uses the ${kind} \`${rule.id}\`, which the sheet lacks: ${why}`
2211
+ });
2212
+ }
2213
+ const token = result.tokens.find((ref) => !tokenKeys.has(`${ref.scale}:${ref.path}`));
2214
+ if (token) ctx.error({
2215
+ code: "MISSING_TOKEN",
2216
+ id,
2217
+ message: `vicss(MISSING_TOKEN): ${file} uses the token \`${token.scale}.${token.path}\`, which the sheet lacks: ${why}`
2218
+ });
2219
+ }
2220
+ /**
2221
+ * A VicssError becomes a located plugin error reading `vicss(CODE): message`
2222
+ * and the hint, with a code frame (PLAN §5.4) as `frame`, which Vite's
2223
+ * overlay and logs and Rolldown's build error all print under the message.
2224
+ * `code` is the source of `id` as compiled (a framework's output, or a
2225
+ * framework file whose scripts compiled with the rest blanked, so lines and
2226
+ * columns are the file's); a location in another file is framed from disk.
2227
+ */
2228
+ function fail(ctx, error, where) {
2229
+ if (!(error instanceof VicssError)) throw error;
2230
+ const { loc, hint } = error;
2231
+ const message = `vicss(${error.code}): ${error.message}${hint ? `\n${hint}` : ""}`;
2232
+ const id = loc?.file ?? where.id;
2233
+ const source = loc && (loc.file === where.id ? where.code : readSource(loc.file));
2234
+ const frame = loc && source !== void 0 ? codeFrame(source, loc.line, loc.column) : void 0;
2235
+ ctx.error({
2236
+ message,
2237
+ code: error.code,
2238
+ ...id ? { id } : {},
2239
+ ...loc ? { loc: {
2240
+ file: loc.file,
2241
+ line: loc.line,
2242
+ column: loc.column
2243
+ } } : {},
2244
+ ...frame ? { frame } : {}
2245
+ });
2246
+ }
2247
+ /**
2248
+ * Vitest serves modules through a dev server (`command: "serve"`) in mode
2249
+ * `test`, or another mode with `--mode`, always with its own `vitest:`
2250
+ * plugins. Its output is asserted and snapshotted, where a source line in
2251
+ * every `sx()` result would break on any unrelated edit above it.
2252
+ */
2253
+ function isTestRunner(config) {
2254
+ return config.mode === "test" || config.plugins.some((plugin) => plugin.name.startsWith("vitest:"));
2255
+ }
2256
+ function readSource(file) {
2257
+ try {
2258
+ return readFileSync(file.replace(/[?#].*$/, ""), "utf8");
2259
+ } catch {
2260
+ return;
2261
+ }
2262
+ }
2263
+ //#endregion
2264
+ export { critical, vicss as default, vicss };