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.
- package/CHANGELOG.md +543 -0
- package/LICENSE +16 -17
- package/NOTICE.md +106 -0
- package/README.md +219 -65
- package/dist/browser.d.ts +4 -2
- package/dist/browser.mjs +12 -4
- package/dist/chunk-2T7V5XLK.mjs +912 -0
- package/dist/chunk-6OORICWF.mjs +16 -0
- package/dist/{chunk-KSNYSR3C.mjs → chunk-FJOZJIKB.mjs} +2499 -329
- package/dist/chunk-L56IRO7A.mjs +491 -0
- package/dist/chunk-PZDVDEZJ.mjs +196 -0
- package/dist/{chunk-3LWJTLOJ.mjs → chunk-RC6DDE4L.mjs} +23 -15
- package/dist/chunk-TQJYVQPE.mjs +217 -0
- package/dist/chunk-W756NVYI.mjs +33 -0
- package/dist/chunk-WBESS2ZD.mjs +598 -0
- package/dist/{chunk-3HRMFZGE.mjs → chunk-X66Z2YHT.mjs} +2 -1
- package/dist/{chunk-6U4IOFOS.mjs → chunk-XQGSG2HK.mjs} +199 -555
- package/dist/cli.mjs +1077 -123
- package/dist/{index-DSgpB6bS.d.ts → context-DcBtnnan.d.ts} +47 -103
- package/dist/editor.d.ts +71 -433
- package/dist/editor.mjs +51 -368
- package/dist/eslint.d.ts +16 -0
- package/dist/eslint.mjs +32 -0
- package/dist/{index-4Kyaq3IZ.d.ts → imports-C9esHd5Q.d.ts} +78 -84
- package/dist/index-CNqdL5U0.d.ts +56 -0
- package/dist/index-Czx-EUwh.d.ts +138 -0
- package/dist/index-DW8YSxTz.d.ts +104 -0
- package/dist/index.d.ts +46 -5
- package/dist/index.mjs +33 -9
- package/dist/oxlint.d.ts +21 -3
- package/dist/oxlint.mjs +19 -1
- package/dist/recipe.d.ts +111 -0
- package/dist/recipe.mjs +71 -0
- package/dist/safelist-CH3_PywB.d.ts +43 -0
- package/dist/session-CMaskdB7.d.ts +543 -0
- package/dist/tailwind.css +644 -0
- package/dist/theme-CIZiGlce.d.ts +115 -0
- package/dist/vite.d.ts +10 -1
- package/dist/vite.mjs +266 -118
- package/package.json +27 -5
- package/dist/chunk-WK6S4HTC.mjs +0 -1921
- package/dist/chunk-ZR7XJMUN.mjs +0 -251
- 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
|
-
|
|
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-
|
|
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-
|
|
13
|
+
} from "./chunk-2T7V5XLK.mjs";
|
|
14
|
+
import {
|
|
15
|
+
createNodeImportResolver
|
|
16
|
+
} from "./chunk-W756NVYI.mjs";
|
|
11
17
|
import {
|
|
12
18
|
isSourceFile
|
|
13
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-X66Z2YHT.mjs";
|
|
14
20
|
import {
|
|
15
21
|
DIRECTIVE_TYPE_NAMES,
|
|
16
22
|
codepointCompare,
|
|
17
|
-
|
|
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-
|
|
34
|
+
} from "./chunk-FJOZJIKB.mjs";
|
|
35
|
+
import "./chunk-L56IRO7A.mjs";
|
|
26
36
|
import {
|
|
27
37
|
devWarn
|
|
28
|
-
} from "./chunk-
|
|
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
|
-
|
|
179
|
-
name: "rainbowindex",
|
|
180
|
-
enforce: "
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
402
|
+
if (extraModules.length > 0) {
|
|
403
|
+
return [...modules, ...extraModules];
|
|
404
|
+
}
|
|
259
405
|
}
|
|
260
|
-
return null;
|
|
261
406
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
signature = [...extractClassesFromSource({ path: file, content })].sort(codepointCompare).join(" ");
|
|
428
|
+
return inlineDirectiveImports(text, { resolve: resolveImport, from: file }).css;
|
|
278
429
|
} catch {
|
|
279
|
-
|
|
430
|
+
return text;
|
|
280
431
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
}
|