rainbowindex 0.6.0 → 0.7.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +543 -0
  2. package/LICENSE +16 -17
  3. package/NOTICE.md +106 -0
  4. package/README.md +219 -65
  5. package/dist/browser.d.ts +4 -2
  6. package/dist/browser.mjs +12 -4
  7. package/dist/chunk-2T7V5XLK.mjs +912 -0
  8. package/dist/chunk-6OORICWF.mjs +16 -0
  9. package/dist/{chunk-KSNYSR3C.mjs → chunk-FJOZJIKB.mjs} +2499 -329
  10. package/dist/chunk-L56IRO7A.mjs +491 -0
  11. package/dist/chunk-PZDVDEZJ.mjs +196 -0
  12. package/dist/{chunk-3LWJTLOJ.mjs → chunk-RC6DDE4L.mjs} +23 -15
  13. package/dist/chunk-TQJYVQPE.mjs +217 -0
  14. package/dist/chunk-W756NVYI.mjs +33 -0
  15. package/dist/chunk-WBESS2ZD.mjs +598 -0
  16. package/dist/{chunk-3HRMFZGE.mjs → chunk-X66Z2YHT.mjs} +2 -1
  17. package/dist/{chunk-6U4IOFOS.mjs → chunk-XQGSG2HK.mjs} +199 -555
  18. package/dist/cli.mjs +1077 -123
  19. package/dist/{index-DSgpB6bS.d.ts → context-DcBtnnan.d.ts} +47 -103
  20. package/dist/editor.d.ts +71 -433
  21. package/dist/editor.mjs +51 -368
  22. package/dist/eslint.d.ts +16 -0
  23. package/dist/eslint.mjs +32 -0
  24. package/dist/{index-4Kyaq3IZ.d.ts → imports-C9esHd5Q.d.ts} +78 -84
  25. package/dist/index-CNqdL5U0.d.ts +56 -0
  26. package/dist/index-Czx-EUwh.d.ts +138 -0
  27. package/dist/index-DW8YSxTz.d.ts +104 -0
  28. package/dist/index.d.ts +46 -5
  29. package/dist/index.mjs +33 -9
  30. package/dist/oxlint.d.ts +21 -3
  31. package/dist/oxlint.mjs +19 -1
  32. package/dist/recipe.d.ts +111 -0
  33. package/dist/recipe.mjs +71 -0
  34. package/dist/safelist-CH3_PywB.d.ts +43 -0
  35. package/dist/session-CMaskdB7.d.ts +543 -0
  36. package/dist/tailwind.css +644 -0
  37. package/dist/theme-CIZiGlce.d.ts +115 -0
  38. package/dist/vite.d.ts +10 -1
  39. package/dist/vite.mjs +266 -118
  40. package/package.json +27 -5
  41. package/dist/chunk-WK6S4HTC.mjs +0 -1921
  42. package/dist/chunk-ZR7XJMUN.mjs +0 -251
  43. package/dist/safelist-CGCtF-Fr.d.ts +0 -96
@@ -0,0 +1,115 @@
1
+ import { E as EditorSession } from './session-CMaskdB7.js';
2
+
3
+ /**
4
+ * Two lint rules over the editor API, in the shape both Oxlint and ESLint read.
5
+ *
6
+ * Neither rule walks the AST for class positions, and that is deliberate. The
7
+ * scanner already decides what a class position is — a `class`/`className`
8
+ * attribute, an argument to a class helper, a `cva`/`tv` config, a safelist —
9
+ * and a rule that re-derived that from the AST would drift from the compiler
10
+ * it is supposed to be describing. So both rules run once per file over the
11
+ * source text, take the scanner's own candidates with their exact spans, and
12
+ * report on those. A class the build compiles and a class the linter checks are
13
+ * then the same set by construction.
14
+ *
15
+ * The rule objects use only the slice of the linter API the two hosts share:
16
+ * `create(context)`, `context.report({ message, node, loc })`, and a visitor
17
+ * keyed by node type. ESLint suggestions are attached where the host supports
18
+ * them, and ignored where it does not.
19
+ */
20
+ interface LintPosition {
21
+ line: number;
22
+ column: number;
23
+ }
24
+ interface LintLoc {
25
+ start: LintPosition;
26
+ end: LintPosition;
27
+ }
28
+ interface LintFix {
29
+ range: [number, number];
30
+ text: string;
31
+ }
32
+ interface LintFixer {
33
+ replaceTextRange(range: [number, number], text: string): LintFix;
34
+ }
35
+ interface LintSuggestion {
36
+ desc: string;
37
+ fix(fixer: LintFixer): LintFix;
38
+ }
39
+ interface LintReport {
40
+ message: string;
41
+ node?: unknown;
42
+ loc?: LintLoc;
43
+ suggest?: LintSuggestion[];
44
+ }
45
+ interface LintSourceCode {
46
+ getText(): string;
47
+ getLocFromIndex(index: number): LintPosition;
48
+ }
49
+ interface LintContext {
50
+ report(report: LintReport): void;
51
+ /** ESLint ≥ 8.40 and Oxlint both expose this; older ESLint used a getter. */
52
+ sourceCode?: LintSourceCode;
53
+ getSourceCode?(): LintSourceCode;
54
+ filename?: string;
55
+ getFilename?(): string;
56
+ options?: readonly unknown[];
57
+ cwd?: string;
58
+ }
59
+ /** Node type → handler. A rule with nothing to do returns an empty one. */
60
+ type LintVisitor = Partial<Record<string, (node: unknown) => void>>;
61
+ interface LintRule {
62
+ meta: {
63
+ type: "problem" | "suggestion";
64
+ docs: {
65
+ description: string;
66
+ };
67
+ hasSuggestions?: boolean;
68
+ schema?: readonly unknown[];
69
+ };
70
+ create(context: LintContext): LintVisitor;
71
+ }
72
+ declare const noUnknownClassRule: LintRule;
73
+ declare const noConflictingClassesRule: LintRule;
74
+ /** The rules both entry points expose, under the names both hosts use. */
75
+ declare const rules: Readonly<Record<string, LintRule>>;
76
+
77
+ /**
78
+ * Finding a project's theme from inside a linter.
79
+ *
80
+ * A lint rule that knows what a class *means* needs the compiled theme, and a
81
+ * linter gives it no build context — just a file and a working directory. So
82
+ * this locates the CSS entry the way the editor toolkit does (the same
83
+ * candidate list, the same activation test), reads it with the same `@import`
84
+ * inliner a build uses, and hands back an `EditorSession`.
85
+ *
86
+ * One session is cached per resolved entry, because a lint run calls into it
87
+ * once per file and rebuilding the theme each time would dominate the run. The
88
+ * cache is invalidated by mtime — on the entry and on every file it imports —
89
+ * so a watching linter picks up a token change without a restart.
90
+ */
91
+
92
+ interface ThemeSourceOptions {
93
+ /**
94
+ * The project's CSS entry, absolute or relative to `cwd`. Set it when the
95
+ * entry is somewhere the candidate list does not look, or to skip the
96
+ * search entirely.
97
+ */
98
+ css?: string;
99
+ /** Where the search starts. Defaults to the linter's working directory. */
100
+ cwd?: string;
101
+ }
102
+ /** The entry the candidate search settled on, or null when there is none. */
103
+ declare function findCSSEntry(cwd: string): string | null;
104
+ /**
105
+ * The session for a project, or null when no Rainbow Index entry was found.
106
+ *
107
+ * Returning null rather than throwing is the point: a rule that cannot find a
108
+ * theme has nothing to say, and a linter run over a repository that does not
109
+ * use Rainbow Index must stay silent rather than fail.
110
+ */
111
+ declare function getSession(options?: ThemeSourceOptions): EditorSession | null;
112
+ /** Drop every cached session. For tests, and for a host that knows better. */
113
+ declare function clearSessionCache(): void;
114
+
115
+ export { type LintRule as L, type ThemeSourceOptions as T, type LintContext as a, type LintFix as b, type LintFixer as c, type LintLoc as d, type LintReport as e, type LintSourceCode as f, type LintSuggestion as g, type LintVisitor as h, clearSessionCache as i, findCSSEntry as j, getSession as k, noUnknownClassRule as l, noConflictingClassesRule as n, rules as r };
package/dist/vite.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- declare function rainbowindexVite(): Plugin;
3
+ /**
4
+ * A pair, because the two halves need opposite ends of Vite's pipeline.
5
+ *
6
+ * `rainbowindex` runs `pre`: it has to read a CSS entry before Vite inlines its
7
+ * `@import` at-rules. `rainbowindex:snapshot` runs `post`: it has to prepend an
8
+ * import to real JavaScript, after every framework compiler has turned a
9
+ * component into some. Vite flattens a returned array, so `plugins:
10
+ * [rainbowindex()]` is unchanged for the caller.
11
+ */
12
+ declare function rainbowindexVite(): Plugin[];
4
13
 
5
14
  export { rainbowindexVite as default };
package/dist/vite.mjs CHANGED
@@ -1,31 +1,41 @@
1
1
  import {
2
2
  postcss_default
3
- } from "./chunk-3LWJTLOJ.mjs";
3
+ } from "./chunk-RC6DDE4L.mjs";
4
+ import {
5
+ snapshotFromCSS
6
+ } from "./chunk-6OORICWF.mjs";
4
7
  import {
5
8
  disableScanChangeTracking,
6
9
  enableScanChangeTracking,
7
10
  enableSourceFileListCache,
8
11
  invalidateSourceFileListCache,
9
12
  markSourceFileChanged
10
- } from "./chunk-WK6S4HTC.mjs";
13
+ } from "./chunk-2T7V5XLK.mjs";
14
+ import {
15
+ createNodeImportResolver
16
+ } from "./chunk-W756NVYI.mjs";
11
17
  import {
12
18
  isSourceFile
13
- } from "./chunk-3HRMFZGE.mjs";
19
+ } from "./chunk-X66Z2YHT.mjs";
14
20
  import {
15
21
  DIRECTIVE_TYPE_NAMES,
16
22
  codepointCompare,
17
- expandApplyGroups,
23
+ expandGroupsInStylesheet,
18
24
  extractClassesFromSource,
19
25
  findClosingBrace,
20
26
  hasRIActivation,
27
+ inlineDirectiveImports,
21
28
  isAtRuleBoundary,
22
29
  isAtRuleNameChar,
23
30
  parseFileDisables,
31
+ scanEntries,
32
+ stripCSSComments,
24
33
  warningCode
25
- } from "./chunk-KSNYSR3C.mjs";
34
+ } from "./chunk-FJOZJIKB.mjs";
35
+ import "./chunk-L56IRO7A.mjs";
26
36
  import {
27
37
  devWarn
28
- } from "./chunk-6U4IOFOS.mjs";
38
+ } from "./chunk-XQGSG2HK.mjs";
29
39
 
30
40
  // src/integrations/vite.ts
31
41
  import { existsSync } from "fs";
@@ -151,9 +161,72 @@ function rewriteDirectiveBodies(code) {
151
161
  if (last === 0) return code;
152
162
  return out + code.slice(last);
153
163
  }
164
+ var BLOCK_AFTER_DECLARATION_DIRECTIVES = /* @__PURE__ */ new Set([
165
+ "font",
166
+ "animate",
167
+ "color"
168
+ ]);
169
+ var LEGACY_APPLY_GROUP_RE = /@a(?:pply)?\b[^;{}]*[\w@-]+:\{/;
170
+ function usesLegacyDirectiveSyntax(code) {
171
+ if (LEGACY_APPLY_GROUP_RE.test(code)) return true;
172
+ let i = 0;
173
+ while (i < code.length) {
174
+ const at = code.indexOf("@", i);
175
+ if (at === -1) return false;
176
+ if (!isAtRuleBoundary(code, at)) {
177
+ i = at + 1;
178
+ continue;
179
+ }
180
+ let nameEnd = at + 1;
181
+ while (nameEnd < code.length && isAtRuleNameChar(code.charCodeAt(nameEnd))) nameEnd++;
182
+ const name = code.slice(at + 1, nameEnd);
183
+ const removals = REMOVAL_BODY_DIRECTIVES.has(name);
184
+ const keywords = KEYWORD_BODY_DIRECTIVES.has(name);
185
+ const blocks = BLOCK_AFTER_DECLARATION_DIRECTIVES.has(name);
186
+ if (!removals && !keywords && !blocks) {
187
+ i = nameEnd;
188
+ continue;
189
+ }
190
+ let braceIdx = nameEnd;
191
+ while (braceIdx < code.length) {
192
+ const ch = code[braceIdx];
193
+ if (ch === "{" || ch === ";" || ch === "}") break;
194
+ braceIdx++;
195
+ }
196
+ if (code[braceIdx] !== "{") {
197
+ i = braceIdx + 1;
198
+ continue;
199
+ }
200
+ const close = findClosingBrace(code, braceIdx);
201
+ if (close === -1) return true;
202
+ const body = code.slice(braceIdx + 1, close);
203
+ if (removals && matches(REMOVAL_RE, stripBlocks(body))) return true;
204
+ if (keywords && matches(FLUID_KEYWORD_RE, stripBlocks(body))) return true;
205
+ if (name === "color" && matches(COLOR_FLAG_RE, body)) return true;
206
+ if (blocks) {
207
+ for (const entry of scanEntries(stripCSSComments(body), {
208
+ newlineTerminates: name !== "animate"
209
+ })) {
210
+ if (entry.key !== "" && entry.value !== "" && entry.block !== void 0) return true;
211
+ }
212
+ }
213
+ i = close + 1;
214
+ }
215
+ return false;
216
+ }
217
+ function matches(pattern, text) {
218
+ pattern.lastIndex = 0;
219
+ return pattern.test(text);
220
+ }
221
+ function stripBlocks(body) {
222
+ return mapTopLevelSpans(body, keepSpan, (interior) => " ".repeat(interior.length));
223
+ }
154
224
 
155
225
  // src/integrations/vite.ts
156
226
  var CSS_FILE_RE = /\.(?:module\.)?css$/;
227
+ var SNAPSHOT_ID = "virtual:rainbowindex/snapshot";
228
+ var RESOLVED_SNAPSHOT_ID = `\0${SNAPSHOT_ID}`;
229
+ var RI_IMPORT_RE = /from\s*["']rainbowindex["']/;
157
230
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
158
231
  var POSTCSS_CONFIG_FILES = [
159
232
  "postcss.config.js",
@@ -171,134 +244,200 @@ function rainbowindexVite() {
171
244
  let root = process.cwd();
172
245
  let logger;
173
246
  const riCSSFiles = /* @__PURE__ */ new Set();
247
+ const riCSSSources = /* @__PURE__ */ new Map();
248
+ let devServer;
174
249
  const fileVersions = /* @__PURE__ */ new Map();
175
250
  const candidateSignatures = /* @__PURE__ */ new Map();
176
251
  let hotUpdateCount = 0;
177
252
  const PRUNE_INTERVAL = 50;
178
- return {
179
- name: "rainbowindex",
180
- enforce: "pre",
181
- async config(config) {
182
- root = config.root ?? process.cwd();
183
- const patch = {};
184
- const ignorePatterns = await riStylesheetPatterns(root);
185
- if (ignorePatterns.length > 0) {
186
- patch.fmt = { ignorePatterns };
187
- }
188
- if (!hasLocalPostCSSConfig(root)) {
189
- patch.css = {
190
- postcss: {
191
- plugins: [postcss_default()]
192
- }
193
- };
194
- }
195
- return patch;
196
- },
197
- configureServer(server) {
198
- root = server.config?.root ?? process.cwd();
199
- enableSourceFileListCache();
200
- server.watcher?.on("add", invalidateSourceFileListCache);
201
- server.watcher?.on("unlink", invalidateSourceFileListCache);
202
- server.watcher?.on("unlinkDir", invalidateSourceFileListCache);
203
- server.httpServer?.once("close", invalidateSourceFileListCache);
204
- enableScanChangeTracking();
205
- server.watcher?.on("change", (file) => markSourceFileChanged(file));
206
- server.httpServer?.once("close", disableScanChangeTracking);
207
- logger = {
208
- info: (msg) => server.config.logger?.info?.(msg, { timestamp: true }),
209
- warn: (msg) => server.config.logger?.warn?.(msg, { timestamp: true })
253
+ const injectSnapshot = {
254
+ name: "rainbowindex:snapshot",
255
+ enforce: "post",
256
+ transform(code, id) {
257
+ if (id === RESOLVED_SNAPSHOT_ID || !RI_IMPORT_RE.test(code)) return null;
258
+ return {
259
+ code: `import ${JSON.stringify(SNAPSHOT_ID)};${code}`,
260
+ map: { mappings: "" }
210
261
  };
211
- if (hasLocalPostCSSConfig(root)) {
212
- logger.info("[rainbowindex] Using local PostCSS config \u2014 skipped auto-injection.");
213
- } else {
214
- logger.info("[rainbowindex] Injected PostCSS plugin (no local postcss.config.* found).");
215
- }
216
- server.httpServer?.once("listening", async () => {
217
- const cssFiles = [];
218
- const fileMap = server.moduleGraph.fileToModulesMap;
219
- if (fileMap) {
220
- for (const [file] of fileMap) {
221
- if (CSS_FILE_RE.test(file)) {
222
- cssFiles.push(file);
262
+ }
263
+ };
264
+ return [
265
+ {
266
+ name: "rainbowindex",
267
+ enforce: "pre",
268
+ async config(config) {
269
+ root = config.root ?? process.cwd();
270
+ const patch = {};
271
+ const ignorePatterns = await riStylesheetPatterns(root);
272
+ if (ignorePatterns.length > 0) {
273
+ patch.fmt = { ignorePatterns };
274
+ }
275
+ if (!hasLocalPostCSSConfig(root)) {
276
+ patch.css = {
277
+ postcss: {
278
+ plugins: [postcss_default()]
223
279
  }
280
+ };
281
+ }
282
+ return patch;
283
+ },
284
+ configureServer(server) {
285
+ root = server.config?.root ?? process.cwd();
286
+ devServer = server;
287
+ enableSourceFileListCache();
288
+ server.watcher?.on("add", invalidateSourceFileListCache);
289
+ server.watcher?.on("unlink", invalidateSourceFileListCache);
290
+ server.watcher?.on("unlinkDir", invalidateSourceFileListCache);
291
+ server.httpServer?.once("close", invalidateSourceFileListCache);
292
+ enableScanChangeTracking();
293
+ server.watcher?.on("change", (file) => markSourceFileChanged(file));
294
+ server.httpServer?.once("close", disableScanChangeTracking);
295
+ logger = {
296
+ info: (msg) => server.config.logger?.info?.(msg, { timestamp: true }),
297
+ warn: (msg) => server.config.logger?.warn?.(msg, { timestamp: true })
298
+ };
299
+ if (hasLocalPostCSSConfig(root)) {
300
+ logger.info("[rainbowindex] Using local PostCSS config \u2014 skipped auto-injection.");
301
+ } else {
302
+ logger.info("[rainbowindex] Injected PostCSS plugin (no local postcss.config.* found).");
303
+ }
304
+ server.httpServer?.once("listening", async () => {
305
+ const cssFiles = [];
306
+ const fileMap = server.moduleGraph.fileToModulesMap;
307
+ if (fileMap) {
308
+ for (const [file] of fileMap) {
309
+ if (CSS_FILE_RE.test(file)) {
310
+ cssFiles.push(file);
311
+ }
312
+ }
313
+ }
314
+ if (cssFiles.length === 0) {
315
+ const diskCSS = await findCSSFilesOnDisk(root);
316
+ cssFiles.push(...diskCSS);
317
+ }
318
+ await Promise.all(cssFiles.map((file) => checkCSSFileAsync(file)));
319
+ if (riCSSFiles.size === 0) {
320
+ logger?.warn(
321
+ `[RI-1602] rainbowindex Vite plugin is registered but no CSS entry with \`@import "rainbowindex"\` was found under ${root}. Create one (e.g. src/index.css) and import it from your app entry, then restart the dev server. Or run \`rainbowindex init\` to wire it up automatically.`
322
+ );
323
+ } else {
324
+ const list = [...riCSSFiles].map((f) => relative(root, f).replaceAll("\\", "/")).join(", ");
325
+ logger?.info(`[rainbowindex] CSS entries: ${list}`);
326
+ }
327
+ });
328
+ },
329
+ resolveId(id) {
330
+ return id === SNAPSHOT_ID ? RESOLVED_SNAPSHOT_ID : null;
331
+ },
332
+ async load(id) {
333
+ if (id !== RESOLVED_SNAPSHOT_ID) return null;
334
+ if (riCSSSources.size === 0) await seedCSSSourcesFromDisk();
335
+ const snapshot = buildClientSnapshot();
336
+ return `import { hydrateSnapshot, publishSnapshot } from "rainbowindex";
337
+ publishSnapshot(hydrateSnapshot(${JSON.stringify(snapshot)}));
338
+ if (import.meta.hot) import.meta.hot.accept();
339
+ `;
340
+ },
341
+ transform(code, id) {
342
+ const file = id.split("?")[0];
343
+ if (CSS_FILE_RE.test(file)) {
344
+ fileVersions.set(file, (fileVersions.get(file) ?? 0) + 1);
345
+ if (hasRIActivation(code)) {
346
+ riCSSFiles.add(file);
347
+ if (riCSSSources.get(file) !== code) {
348
+ riCSSSources.set(file, code);
349
+ invalidateClientSnapshot();
350
+ }
351
+ let safe = rewriteDirectiveBodies(code);
352
+ const expandWarnings = [];
353
+ safe = expandGroupsInStylesheet(safe, expandWarnings, file);
354
+ const suppressed = parseFileDisables(code);
355
+ for (const w of expandWarnings) {
356
+ const warned = warningCode(w);
357
+ if (warned !== null && suppressed.has(warned)) continue;
358
+ (logger?.warn ?? console.warn)(`[rainbowindex] ${w}`);
359
+ }
360
+ return safe !== code ? safe : null;
224
361
  }
362
+ riCSSFiles.delete(file);
363
+ if (riCSSSources.delete(file)) invalidateClientSnapshot();
225
364
  }
226
- if (cssFiles.length === 0) {
227
- const diskCSS = await findCSSFilesOnDisk(root);
228
- cssFiles.push(...diskCSS);
365
+ return null;
366
+ },
367
+ async handleHotUpdate(ctx) {
368
+ const { file, server, modules } = ctx;
369
+ if (++hotUpdateCount % PRUNE_INTERVAL === 0) {
370
+ await pruneDeletedFiles();
229
371
  }
230
- await Promise.all(cssFiles.map((file) => checkCSSFileAsync(file)));
231
- if (riCSSFiles.size === 0) {
232
- logger?.warn(
233
- `[RI-1602] rainbowindex Vite plugin is registered but no CSS entry with \`@import "rainbowindex"\` was found under ${root}. Create one (e.g. src/index.css) and import it from your app entry, then restart the dev server. Or run \`rainbowindex init\` to wire it up automatically.`
234
- );
235
- } else {
236
- const list = [...riCSSFiles].map((f) => relative(root, f).replaceAll("\\", "/")).join(", ");
237
- logger?.info(`[rainbowindex] CSS entries: ${list}`);
372
+ if (CSS_FILE_RE.test(file)) {
373
+ await checkCSSFileAsync(file);
374
+ return;
238
375
  }
239
- });
240
- },
241
- transform(code, id) {
242
- const file = id.split("?")[0];
243
- if (CSS_FILE_RE.test(file)) {
244
- fileVersions.set(file, (fileVersions.get(file) ?? 0) + 1);
245
- if (hasRIActivation(code)) {
246
- riCSSFiles.add(file);
247
- let safe = rewriteDirectiveBodies(code);
248
- const expandWarnings = [];
249
- safe = expandApplyGroups(safe, expandWarnings, file);
250
- const suppressed = parseFileDisables(code);
251
- for (const w of expandWarnings) {
252
- const warned = warningCode(w);
253
- if (warned !== null && suppressed.has(warned)) continue;
254
- (logger?.warn ?? console.warn)(`[rainbowindex] ${w}`);
376
+ if (!isSourceFile(file)) return;
377
+ markSourceFileChanged(file);
378
+ const previous = candidateSignatures.get(file);
379
+ let signature;
380
+ try {
381
+ const content = await ctx.read();
382
+ signature = [...extractClassesFromSource({ path: file, content })].sort(codepointCompare).join(" ");
383
+ } catch {
384
+ candidateSignatures.delete(file);
385
+ }
386
+ if (signature !== void 0) {
387
+ candidateSignatures.set(file, signature);
388
+ if (previous === signature) return;
389
+ }
390
+ const extraModules = [];
391
+ const hmrModules = new Set(modules);
392
+ for (const cssFile of riCSSFiles) {
393
+ const mods = server.moduleGraph.getModulesByFile(cssFile);
394
+ if (mods) {
395
+ for (const mod of mods) {
396
+ if (!hmrModules.has(mod)) {
397
+ extraModules.push(mod);
398
+ }
399
+ }
255
400
  }
256
- return safe !== code ? safe : null;
257
401
  }
258
- riCSSFiles.delete(file);
402
+ if (extraModules.length > 0) {
403
+ return [...modules, ...extraModules];
404
+ }
259
405
  }
260
- return null;
261
406
  },
262
- async handleHotUpdate(ctx) {
263
- const { file, server, modules } = ctx;
264
- if (++hotUpdateCount % PRUNE_INTERVAL === 0) {
265
- await pruneDeletedFiles();
266
- }
267
- if (CSS_FILE_RE.test(file)) {
268
- await checkCSSFileAsync(file);
269
- return;
270
- }
271
- if (!isSourceFile(file)) return;
272
- markSourceFileChanged(file);
273
- const previous = candidateSignatures.get(file);
274
- let signature;
407
+ injectSnapshot
408
+ ];
409
+ async function seedCSSSourcesFromDisk() {
410
+ const files = await findCSSFilesOnDisk(root);
411
+ await Promise.all(
412
+ files.map(async (file) => {
413
+ try {
414
+ const content = await readFile(file, "utf-8");
415
+ if (!hasRIActivation(content)) return;
416
+ riCSSFiles.add(file);
417
+ riCSSSources.set(file, content);
418
+ } catch {
419
+ }
420
+ })
421
+ );
422
+ }
423
+ function buildClientSnapshot() {
424
+ const resolveImport = createNodeImportResolver({ cwd: root });
425
+ const css = [...riCSSSources.keys()].sort(codepointCompare).map((file) => {
426
+ const text = riCSSSources.get(file) ?? "";
275
427
  try {
276
- const content = await ctx.read();
277
- signature = [...extractClassesFromSource({ path: file, content })].sort(codepointCompare).join(" ");
428
+ return inlineDirectiveImports(text, { resolve: resolveImport, from: file }).css;
278
429
  } catch {
279
- candidateSignatures.delete(file);
430
+ return text;
280
431
  }
281
- if (signature !== void 0) {
282
- candidateSignatures.set(file, signature);
283
- if (previous === signature) return;
284
- }
285
- const extraModules = [];
286
- const hmrModules = new Set(modules);
287
- for (const cssFile of riCSSFiles) {
288
- const mods = server.moduleGraph.getModulesByFile(cssFile);
289
- if (mods) {
290
- for (const mod of mods) {
291
- if (!hmrModules.has(mod)) {
292
- extraModules.push(mod);
293
- }
294
- }
295
- }
296
- }
297
- if (extraModules.length > 0) {
298
- return [...modules, ...extraModules];
299
- }
300
- }
301
- };
432
+ }).join("\n");
433
+ return snapshotFromCSS(css);
434
+ }
435
+ function invalidateClientSnapshot() {
436
+ const graph = devServer?.moduleGraph;
437
+ if (!graph) return;
438
+ const mod = graph.getModuleById(RESOLVED_SNAPSHOT_ID);
439
+ if (mod) graph.invalidateModule(mod);
440
+ }
302
441
  async function pruneDeletedFiles() {
303
442
  const tracked = /* @__PURE__ */ new Set([...riCSSFiles, ...fileVersions.keys(), ...candidateSignatures.keys()]);
304
443
  const checks = [...tracked].map(async (file) => {
@@ -306,6 +445,7 @@ function rainbowindexVite() {
306
445
  await access(file);
307
446
  } catch {
308
447
  riCSSFiles.delete(file);
448
+ if (riCSSSources.delete(file)) invalidateClientSnapshot();
309
449
  fileVersions.delete(file);
310
450
  candidateSignatures.delete(file);
311
451
  }
@@ -337,7 +477,9 @@ function rainbowindexVite() {
337
477
  const active = await Promise.all(
338
478
  files.map(async (file) => {
339
479
  try {
340
- return hasRIActivation(await readFile(file, "utf-8")) ? file : null;
480
+ const code = await readFile(file, "utf-8");
481
+ if (!hasRIActivation(code)) return null;
482
+ return usesLegacyDirectiveSyntax(code) ? file : null;
341
483
  } catch {
342
484
  return null;
343
485
  }
@@ -357,12 +499,18 @@ function rainbowindexVite() {
357
499
  if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
358
500
  if (hasRIActivation(raw)) {
359
501
  riCSSFiles.add(file);
502
+ if (riCSSSources.get(file) !== raw) {
503
+ riCSSSources.set(file, raw);
504
+ invalidateClientSnapshot();
505
+ }
360
506
  } else {
361
507
  riCSSFiles.delete(file);
508
+ if (riCSSSources.delete(file)) invalidateClientSnapshot();
362
509
  }
363
510
  } catch (_err) {
364
511
  if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
365
512
  riCSSFiles.delete(file);
513
+ if (riCSSSources.delete(file)) invalidateClientSnapshot();
366
514
  }
367
515
  }
368
516
  }