rainbowindex 0.1.4 → 0.2.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 +122 -0
- package/{LICENSE.md → LICENSE} +3 -3
- package/README.md +262 -125
- package/dist/browser.d.ts +3 -1321
- package/dist/browser.mjs +41 -20
- package/dist/chunk-5N4GPK26.mjs +2664 -0
- package/dist/chunk-KCSNR2TV.mjs +671 -0
- package/dist/chunk-PD4ZXGJ6.mjs +14 -0
- package/dist/chunk-RPXZ3O6R.mjs +10518 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +1258 -0
- package/dist/index.css +2 -0
- package/dist/index.d.ts +266 -5207
- package/dist/index.mjs +70 -68
- package/dist/optimize-6NWJVT6W.mjs +24 -0
- package/dist/safelist-DRk1XXxi.d.ts +239 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.mjs +320 -0
- package/package.json +79 -66
- package/css/index.css +0 -5
- package/css/preflight.css +0 -370
- package/css/theme.css +0 -308
- package/dist/browser.mjs.map +0 -1
- package/dist/index.cjs +0 -69
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -5230
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/cli/optimize.ts
|
|
2
|
+
import { transform } from "lightningcss";
|
|
3
|
+
function optimizeCSS(css) {
|
|
4
|
+
try {
|
|
5
|
+
const result = transform({
|
|
6
|
+
filename: "output.css",
|
|
7
|
+
code: Buffer.from(css),
|
|
8
|
+
minify: true,
|
|
9
|
+
targets: {
|
|
10
|
+
chrome: 96 << 16,
|
|
11
|
+
firefox: 91 << 16,
|
|
12
|
+
safari: 15 << 16 | 4 << 8
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return result.code.toString();
|
|
16
|
+
} catch (err) {
|
|
17
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18
|
+
const sanitized = msg.replace(/\/[a-zA-Z][\w.-]*(?:\/[\w.-]+)+/g, "<path>").replace(/[A-Z]:\\[\w.-]+(?:\\[\w.-]+)+/gi, "<path>");
|
|
19
|
+
throw new Error(`CSS optimization failed: ${sanitized}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
optimizeCSS
|
|
24
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generative color system — color domain model, OKLCH ramp generation,
|
|
3
|
+
* and light-dark() pairing.
|
|
4
|
+
*
|
|
5
|
+
* 2 numbers (chroma + hue) → 19-stop palette with automatic dark mode.
|
|
6
|
+
*
|
|
7
|
+
* The ramp is sampled from a fixed reference profile (`L_PROFILE` / `C_SHAPE` /
|
|
8
|
+
* `H_DRIFT`, captured from the reference palette): lightness is a curved,
|
|
9
|
+
* compressed scale (low suffix → light, high → dark), chroma is an asymmetric
|
|
10
|
+
* bell peaking at stop 500, and hue is near-flat. Dark mode is a simple ramp
|
|
11
|
+
* reversal — the dark value is the stop the ramp reaches at the mirror position
|
|
12
|
+
* (`1000 - suffix`), so stop 500 pivots to itself.
|
|
13
|
+
*/
|
|
14
|
+
/** Per-color dark mode override strategy. */
|
|
15
|
+
type ColorDarkOverride = {
|
|
16
|
+
strategy: "mirror";
|
|
17
|
+
} | {
|
|
18
|
+
strategy: "fixed";
|
|
19
|
+
} | {
|
|
20
|
+
strategy: "shift";
|
|
21
|
+
chromaDelta: number;
|
|
22
|
+
hueDelta: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Color definition — discriminated union supporting:
|
|
26
|
+
* - Generative: `brand: 0.18 330;` → 19-stop palette with auto dark mode
|
|
27
|
+
* - Explicit: `accent: oklch(0.72 0.21 330);` → single color value
|
|
28
|
+
* - Pair: `surface: oklch(0.98 0.01 260) / oklch(0.15 0.01 260);` → light/dark pair
|
|
29
|
+
* - Alias: `theme: brand;` → references another color's palette via var()
|
|
30
|
+
*/
|
|
31
|
+
type ColorDefinition = {
|
|
32
|
+
type: "generative";
|
|
33
|
+
chroma: number;
|
|
34
|
+
hue: number;
|
|
35
|
+
dark?: ColorDarkOverride;
|
|
36
|
+
inline?: boolean;
|
|
37
|
+
parabolic?: boolean;
|
|
38
|
+
chromaBoost?: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
type: "explicit";
|
|
41
|
+
value: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "keyword";
|
|
44
|
+
value: string;
|
|
45
|
+
} | {
|
|
46
|
+
type: "pair";
|
|
47
|
+
light: string;
|
|
48
|
+
dark: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: "alias";
|
|
51
|
+
source: string;
|
|
52
|
+
};
|
|
53
|
+
interface DarkModeConfig {
|
|
54
|
+
mode: "auto" | "off";
|
|
55
|
+
chromaBoost: number;
|
|
56
|
+
hueShift: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default theme values — the static data that ships if the user writes no directives.
|
|
61
|
+
* `directives.ts` (Phase 5) parses user overrides and merges them with these defaults.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
interface TextSize {
|
|
65
|
+
fontSize: string;
|
|
66
|
+
lineHeight: string;
|
|
67
|
+
}
|
|
68
|
+
type FluidUnit = "vw" | "vi" | "vmin" | "vmax";
|
|
69
|
+
interface FluidConfig {
|
|
70
|
+
min: string;
|
|
71
|
+
max: string;
|
|
72
|
+
unit?: FluidUnit;
|
|
73
|
+
multiplier?: number;
|
|
74
|
+
}
|
|
75
|
+
interface AnimationDefinition {
|
|
76
|
+
shorthand: string;
|
|
77
|
+
keyframes: string;
|
|
78
|
+
}
|
|
79
|
+
interface Theme {
|
|
80
|
+
spacing: {
|
|
81
|
+
base: string;
|
|
82
|
+
};
|
|
83
|
+
colors: Record<string, ColorDefinition>;
|
|
84
|
+
text: Record<string, TextSize>;
|
|
85
|
+
breakpoints: Record<string, string>;
|
|
86
|
+
rounded: Record<string, string>;
|
|
87
|
+
shadows: Record<string, string>;
|
|
88
|
+
weights: Record<string, number>;
|
|
89
|
+
easing: Record<string, string>;
|
|
90
|
+
fluid: FluidConfig;
|
|
91
|
+
animations: Record<string, AnimationDefinition>;
|
|
92
|
+
blur: Record<string, string>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Keyword corner-shape values. `superellipse(N)` is represented separately
|
|
96
|
+
* as `{ superellipse: N }` since the numeric parameter isn't a keyword.
|
|
97
|
+
* Single source for the type, the scale table below, and the @rounded
|
|
98
|
+
* modifier parser (directives/parsers.ts).
|
|
99
|
+
*/
|
|
100
|
+
declare const CORNER_SHAPE_KEYWORDS: readonly ["round", "scoop", "bevel", "notch", "square", "squircle"];
|
|
101
|
+
type CornerShapeKeyword = (typeof CORNER_SHAPE_KEYWORDS)[number];
|
|
102
|
+
type CornerShape = CornerShapeKeyword | {
|
|
103
|
+
superellipse: number;
|
|
104
|
+
};
|
|
105
|
+
declare const defaultTheme: Theme;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* ri() — class merge function.
|
|
109
|
+
* Replaces both tailwind-merge and clsx.
|
|
110
|
+
*
|
|
111
|
+
* Re-exported from the package's main and browser entries as `ri()`.
|
|
112
|
+
* Right-to-left scan: rightmost class wins when two classes set the same CSS property.
|
|
113
|
+
*
|
|
114
|
+
* ## Concurrency
|
|
115
|
+
*
|
|
116
|
+
* The default `ri()` export uses module-level state published by
|
|
117
|
+
* `finalizeCompilationContext()`. This is safe for single-compilation
|
|
118
|
+
* environments (typical browser usage, single Vite build, PostCSS).
|
|
119
|
+
*
|
|
120
|
+
* In multi-tenant / SSR / concurrent-compilation environments, use
|
|
121
|
+
* `createRi(snapshot)` instead — it captures a frozen snapshot of the
|
|
122
|
+
* compilation state and uses its own independent cache:
|
|
123
|
+
*
|
|
124
|
+
* const snapshot = finalizeCompilationContext(ctx);
|
|
125
|
+
* const ri = createRi(snapshot);
|
|
126
|
+
*/
|
|
127
|
+
type ClassInput = string | false | null | undefined | ClassInput[];
|
|
128
|
+
declare const DEFAULT_TEXT_SIZES: readonly ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl", "5xl"];
|
|
129
|
+
interface CompilationContext {
|
|
130
|
+
customStaticProps: Record<string, string[]>;
|
|
131
|
+
textSizes: Set<string>;
|
|
132
|
+
fontFamilies: Set<string>;
|
|
133
|
+
colorNames: Set<string>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Frozen snapshot of compilation state for SSR-safe ri() instances.
|
|
137
|
+
* Created by finalizeCompilationContext() and consumed by createRi().
|
|
138
|
+
*/
|
|
139
|
+
interface CompilationSnapshot {
|
|
140
|
+
readonly customStaticProps: Readonly<Record<string, string[]>>;
|
|
141
|
+
readonly textSizes: ReadonlySet<string>;
|
|
142
|
+
readonly fontFamilies: ReadonlySet<string>;
|
|
143
|
+
readonly colorNames: ReadonlySet<string>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Merge class names with conflict resolution (replaces both tailwind-merge and clsx).
|
|
147
|
+
* Rightmost class wins when two classes set the same CSS property.
|
|
148
|
+
* Falsy values are filtered.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ri('p-2 bg-red-500', 'p-4') // → 'bg-red-500 p-4'
|
|
152
|
+
* ri('px-2 py-1', 'p-4') // → 'p-4' (shorthand wins)
|
|
153
|
+
* ri('flex', isActive && 'bg-blue-500') // → 'flex bg-blue-500'
|
|
154
|
+
* ri('text-lg text-red-500') // → 'text-lg text-red-500' (different properties)
|
|
155
|
+
*/
|
|
156
|
+
declare function ri(...inputs: ClassInput[]): string;
|
|
157
|
+
/**
|
|
158
|
+
* Create an isolated ri() instance bound to a specific compilation snapshot.
|
|
159
|
+
* Use this in SSR or multi-compilation environments where the global ri()
|
|
160
|
+
* would be corrupted by concurrent compilations.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const snapshot = finalizeCompilationContext(ctx);
|
|
164
|
+
* const ri = createRi(snapshot);
|
|
165
|
+
* ri('p-2 bg-red-500', 'p-4') // → 'bg-red-500 p-4'
|
|
166
|
+
*/
|
|
167
|
+
declare function createRi(snapshot?: CompilationSnapshot): (...inputs: ClassInput[]) => string;
|
|
168
|
+
/**
|
|
169
|
+
* Create a fresh compilation context.
|
|
170
|
+
* Called at the start of each compile() pass.
|
|
171
|
+
*/
|
|
172
|
+
declare function createCompilationContext(): CompilationContext;
|
|
173
|
+
/**
|
|
174
|
+
* Register a custom utility's CSS properties in the compilation context.
|
|
175
|
+
* Called by the engine when processing @utility directives.
|
|
176
|
+
*/
|
|
177
|
+
declare function registerCustomUtility(ctx: CompilationContext, name: string, properties: string[]): void;
|
|
178
|
+
/**
|
|
179
|
+
* Register custom text sizes so the merge function correctly classifies
|
|
180
|
+
* text-{custom} as a font-size utility rather than a color utility.
|
|
181
|
+
*/
|
|
182
|
+
declare function registerCustomTextSizes(ctx: CompilationContext, sizes: string[]): void;
|
|
183
|
+
/**
|
|
184
|
+
* Register custom font family names so the merge function correctly classifies
|
|
185
|
+
* font-{custom} as a font-family utility rather than a font-weight utility.
|
|
186
|
+
*/
|
|
187
|
+
declare function registerCustomFontFamilies(ctx: CompilationContext, families: string[]): void;
|
|
188
|
+
/**
|
|
189
|
+
* Register the resolved theme's color names so the merge function classifies
|
|
190
|
+
* bare flat colors (border-accent for `@color { accent: … }`) as color
|
|
191
|
+
* utilities rather than width/weight ones. Shaded forms (accent-500) already
|
|
192
|
+
* match the shade pattern and need no registration.
|
|
193
|
+
*/
|
|
194
|
+
declare function registerColorNames(ctx: CompilationContext, names: string[]): void;
|
|
195
|
+
declare function finalizeCompilationContext(ctx: CompilationContext): CompilationSnapshot;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* `safelist()` — declare utility classes that must be emitted regardless of
|
|
199
|
+
* whether the consumer's source files reference them directly.
|
|
200
|
+
*
|
|
201
|
+
* At runtime this is a plain identity-join: pass any number of strings (and
|
|
202
|
+
* falsy values, which are filtered) and receive a single space-joined string
|
|
203
|
+
* suitable for `className`. The function performs no global registration, has
|
|
204
|
+
* no side effects, and is tree-shake-safe.
|
|
205
|
+
*
|
|
206
|
+
* The build-time meaning comes from the scanner: when the source-file
|
|
207
|
+
* extractor encounters a `safelist(...)` call, it extracts every literal
|
|
208
|
+
* string argument as a class declaration — so the classes get emitted in the
|
|
209
|
+
* final CSS even though the consumer's source never names them literally.
|
|
210
|
+
*
|
|
211
|
+
* Primary use case is component libraries that ship classNames inside their
|
|
212
|
+
* bundled code (e.g. a curated icon set whose strokes are described by
|
|
213
|
+
* utility classes). The library wraps its declarations in `safelist(...)`,
|
|
214
|
+
* the consumer's setup points the scanner at the library's `dist/`, and the
|
|
215
|
+
* classes flow through unchanged. The Vite plugin auto-discovers libraries
|
|
216
|
+
* that opt in via a `rainbowindex.safelistSources` field in their
|
|
217
|
+
* `package.json`, so consumers typically don't have to add `@source` lines
|
|
218
|
+
* by hand.
|
|
219
|
+
*
|
|
220
|
+
* const ICON_BASE = safelist("stroke-cap-round", "stroke-join-round");
|
|
221
|
+
* const SidebarLeft = defineIcon({
|
|
222
|
+
* primitives: SIDEBAR,
|
|
223
|
+
* className: safelist(ICON_BASE, "-scale-x-100"),
|
|
224
|
+
* });
|
|
225
|
+
*
|
|
226
|
+
* Scanner contract:
|
|
227
|
+
* - Only STATIC string literals at the call site are extracted. Values
|
|
228
|
+
* passed through variables (`safelist(ICON_BASE, ...)`) won't be re-read
|
|
229
|
+
* at the outer call site, but the original `safelist("stroke-cap-round",
|
|
230
|
+
* ...)` that produced `ICON_BASE` is itself extracted, so the classes are
|
|
231
|
+
* still covered.
|
|
232
|
+
* - Template literals with no `${…}` interpolation are extracted; templates
|
|
233
|
+
* with interpolation are skipped.
|
|
234
|
+
* - Falsy arguments are dropped at runtime so conditional fragments compose
|
|
235
|
+
* naturally: `safelist("flex", side === "left" && "flex-row-reverse")`.
|
|
236
|
+
*/
|
|
237
|
+
declare function safelist(...parts: ReadonlyArray<string | false | null | undefined>): string;
|
|
238
|
+
|
|
239
|
+
export { type AnimationDefinition as A, type ColorDefinition as C, DEFAULT_TEXT_SIZES as D, type FluidConfig as F, type TextSize as T, type CompilationContext as a, type CompilationSnapshot as b, type Theme as c, createCompilationContext as d, createRi as e, defaultTheme as f, finalizeCompilationContext as g, registerCustomFontFamilies as h, registerCustomTextSizes as i, registerCustomUtility as j, ri as k, type DarkModeConfig as l, type CornerShape as m, registerColorNames as r, safelist as s };
|
package/dist/vite.d.ts
ADDED
package/dist/vite.mjs
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
postcss_default
|
|
3
|
+
} from "./chunk-KCSNR2TV.mjs";
|
|
4
|
+
import {
|
|
5
|
+
expandApplyGroups,
|
|
6
|
+
findClosingBrace,
|
|
7
|
+
hasRIActivation,
|
|
8
|
+
isAtRuleBoundary,
|
|
9
|
+
isAtRuleNameChar,
|
|
10
|
+
isSourceFile
|
|
11
|
+
} from "./chunk-RPXZ3O6R.mjs";
|
|
12
|
+
import {
|
|
13
|
+
devWarn
|
|
14
|
+
} from "./chunk-5N4GPK26.mjs";
|
|
15
|
+
|
|
16
|
+
// src/integrations/vite.ts
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
import { access, readFile, readdir } from "fs/promises";
|
|
19
|
+
import { join, relative, resolve } from "path";
|
|
20
|
+
var CSS_FILE_RE = /\.(?:module\.)?css$/;
|
|
21
|
+
var REMOVAL_BODY_DIRECTIVES = /* @__PURE__ */ new Set([
|
|
22
|
+
"color",
|
|
23
|
+
"text",
|
|
24
|
+
"spacing",
|
|
25
|
+
"breakpoint",
|
|
26
|
+
"rounded",
|
|
27
|
+
"shadow",
|
|
28
|
+
"weight",
|
|
29
|
+
"ease",
|
|
30
|
+
"blur",
|
|
31
|
+
"z",
|
|
32
|
+
"animate",
|
|
33
|
+
"leading",
|
|
34
|
+
"tracking",
|
|
35
|
+
"opacity",
|
|
36
|
+
"duration"
|
|
37
|
+
]);
|
|
38
|
+
var KEYWORD_BODY_DIRECTIVES = /* @__PURE__ */ new Set(["fluid"]);
|
|
39
|
+
var REMOVAL_RE = /!([\w][\w-]*)\s*;/g;
|
|
40
|
+
var FLUID_KEYWORD_RE = /\b(no-parabolic|parabolic|no-shift|shift)\s*;?/g;
|
|
41
|
+
var COLOR_FLAG_RE = /(?<=[{;\s]|^)(inline|no-parabolic|parabolic)\s*(?:;|(?=}))/g;
|
|
42
|
+
function rewriteTopLevel(body, rewrite) {
|
|
43
|
+
if (!body.includes("{")) return rewrite(body);
|
|
44
|
+
let out = "";
|
|
45
|
+
let segStart = 0;
|
|
46
|
+
let depth = 0;
|
|
47
|
+
for (let i = 0; i < body.length; i++) {
|
|
48
|
+
const ch = body[i];
|
|
49
|
+
if (ch === "{") {
|
|
50
|
+
if (depth === 0) {
|
|
51
|
+
out += rewrite(body.slice(segStart, i));
|
|
52
|
+
segStart = i;
|
|
53
|
+
}
|
|
54
|
+
depth++;
|
|
55
|
+
} else if (ch === "}") {
|
|
56
|
+
if (depth > 0) depth--;
|
|
57
|
+
if (depth === 0) {
|
|
58
|
+
out += body.slice(segStart, i + 1);
|
|
59
|
+
segStart = i + 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out += depth === 0 ? rewrite(body.slice(segStart)) : body.slice(segStart);
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function rewriteColorOptionFlag(_match, keyword) {
|
|
67
|
+
if (keyword === "inline") return "--ri-inline: true;";
|
|
68
|
+
const negated = keyword.startsWith("no-");
|
|
69
|
+
return `--ri-${negated ? keyword.slice(3) : keyword}: ${negated ? "false" : "true"};`;
|
|
70
|
+
}
|
|
71
|
+
function rewriteColorOptionFlags(body) {
|
|
72
|
+
if (!body.includes("{")) return body;
|
|
73
|
+
let out = "";
|
|
74
|
+
let segStart = 0;
|
|
75
|
+
let depth = 0;
|
|
76
|
+
let blockStart = -1;
|
|
77
|
+
for (let i = 0; i < body.length; i++) {
|
|
78
|
+
const ch = body[i];
|
|
79
|
+
if (ch === "{") {
|
|
80
|
+
if (depth === 0) {
|
|
81
|
+
out += body.slice(segStart, i + 1);
|
|
82
|
+
blockStart = i + 1;
|
|
83
|
+
}
|
|
84
|
+
depth++;
|
|
85
|
+
} else if (ch === "}") {
|
|
86
|
+
if (depth > 0) depth--;
|
|
87
|
+
if (depth === 0 && blockStart !== -1) {
|
|
88
|
+
out += body.slice(blockStart, i).replace(COLOR_FLAG_RE, rewriteColorOptionFlag);
|
|
89
|
+
out += "}";
|
|
90
|
+
segStart = i + 1;
|
|
91
|
+
blockStart = -1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
out += body.slice(segStart);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function rewriteDirectiveBodies(code) {
|
|
99
|
+
let out = "";
|
|
100
|
+
let last = 0;
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (i < code.length) {
|
|
103
|
+
const at = code.indexOf("@", i);
|
|
104
|
+
if (at === -1) break;
|
|
105
|
+
if (!isAtRuleBoundary(code, at)) {
|
|
106
|
+
i = at + 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let nameEnd = at + 1;
|
|
110
|
+
while (nameEnd < code.length && isAtRuleNameChar(code.charCodeAt(nameEnd))) nameEnd++;
|
|
111
|
+
const name = code.slice(at + 1, nameEnd);
|
|
112
|
+
const removals = REMOVAL_BODY_DIRECTIVES.has(name);
|
|
113
|
+
const keywords = KEYWORD_BODY_DIRECTIVES.has(name);
|
|
114
|
+
if (!removals && !keywords) {
|
|
115
|
+
i = nameEnd;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
let braceIdx = nameEnd;
|
|
119
|
+
while (braceIdx < code.length) {
|
|
120
|
+
const ch = code[braceIdx];
|
|
121
|
+
if (ch === "{" || ch === ";" || ch === "}") break;
|
|
122
|
+
braceIdx++;
|
|
123
|
+
}
|
|
124
|
+
if (code[braceIdx] !== "{") {
|
|
125
|
+
i = braceIdx + 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const close = findClosingBrace(code, braceIdx);
|
|
129
|
+
const bodyStart = braceIdx + 1;
|
|
130
|
+
const bodyEnd = close === -1 ? code.length : close;
|
|
131
|
+
let rewritten = rewriteTopLevel(code.slice(bodyStart, bodyEnd), (span) => {
|
|
132
|
+
let s = span;
|
|
133
|
+
if (removals) s = s.replace(REMOVAL_RE, "--ri-rm: $1;");
|
|
134
|
+
if (keywords) {
|
|
135
|
+
s = s.replace(FLUID_KEYWORD_RE, (_, kw) => {
|
|
136
|
+
const negated = kw.startsWith("no-");
|
|
137
|
+
return `--ri-${negated ? kw.slice(3) : kw}: ${negated ? "false" : "true"};`;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return s;
|
|
141
|
+
});
|
|
142
|
+
if (name === "color") rewritten = rewriteColorOptionFlags(rewritten);
|
|
143
|
+
out += code.slice(last, bodyStart) + rewritten;
|
|
144
|
+
last = bodyEnd;
|
|
145
|
+
i = bodyEnd;
|
|
146
|
+
}
|
|
147
|
+
if (last === 0) return code;
|
|
148
|
+
return out + code.slice(last);
|
|
149
|
+
}
|
|
150
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
151
|
+
var POSTCSS_CONFIG_FILES = [
|
|
152
|
+
"postcss.config.js",
|
|
153
|
+
"postcss.config.mjs",
|
|
154
|
+
"postcss.config.ts",
|
|
155
|
+
"postcss.config.cjs"
|
|
156
|
+
];
|
|
157
|
+
function isIgnorableDirectoryReadError(err) {
|
|
158
|
+
return !!err && typeof err === "object" && "code" in err && (err.code === "ENOENT" || err.code === "ENOTDIR" || err.code === "EACCES" || err.code === "EPERM");
|
|
159
|
+
}
|
|
160
|
+
function hasLocalPostCSSConfig(root) {
|
|
161
|
+
return POSTCSS_CONFIG_FILES.some((name) => existsSync(resolve(root, name)));
|
|
162
|
+
}
|
|
163
|
+
function rainbowindexVite() {
|
|
164
|
+
let root = process.cwd();
|
|
165
|
+
let logger;
|
|
166
|
+
const riCSSFiles = /* @__PURE__ */ new Set();
|
|
167
|
+
const fileVersions = /* @__PURE__ */ new Map();
|
|
168
|
+
let hotUpdateCount = 0;
|
|
169
|
+
const PRUNE_INTERVAL = 50;
|
|
170
|
+
return {
|
|
171
|
+
name: "rainbowindex",
|
|
172
|
+
enforce: "pre",
|
|
173
|
+
async config(config) {
|
|
174
|
+
root = config.root ?? process.cwd();
|
|
175
|
+
if (!hasLocalPostCSSConfig(root)) {
|
|
176
|
+
return {
|
|
177
|
+
css: {
|
|
178
|
+
postcss: {
|
|
179
|
+
plugins: [postcss_default()]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {};
|
|
185
|
+
},
|
|
186
|
+
configureServer(server) {
|
|
187
|
+
root = server.config?.root ?? process.cwd();
|
|
188
|
+
logger = {
|
|
189
|
+
info: (msg) => server.config.logger?.info?.(msg, { timestamp: true }),
|
|
190
|
+
warn: (msg) => server.config.logger?.warn?.(msg, { timestamp: true })
|
|
191
|
+
};
|
|
192
|
+
if (hasLocalPostCSSConfig(root)) {
|
|
193
|
+
logger.info("[rainbowindex] Using local PostCSS config \u2014 skipped auto-injection.");
|
|
194
|
+
} else {
|
|
195
|
+
logger.info("[rainbowindex] Injected PostCSS plugin (no local postcss.config.* found).");
|
|
196
|
+
}
|
|
197
|
+
server.httpServer?.once("listening", async () => {
|
|
198
|
+
const cssFiles = [];
|
|
199
|
+
const fileMap = server.moduleGraph.fileToModulesMap;
|
|
200
|
+
if (fileMap) {
|
|
201
|
+
for (const [file] of fileMap) {
|
|
202
|
+
if (CSS_FILE_RE.test(file)) {
|
|
203
|
+
cssFiles.push(file);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (cssFiles.length === 0) {
|
|
208
|
+
const diskCSS = await findCSSFilesOnDisk(root);
|
|
209
|
+
cssFiles.push(...diskCSS);
|
|
210
|
+
}
|
|
211
|
+
await Promise.all(cssFiles.map((file) => checkCSSFileAsync(file)));
|
|
212
|
+
if (riCSSFiles.size === 0) {
|
|
213
|
+
logger?.warn(
|
|
214
|
+
`[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.`
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
const list = [...riCSSFiles].map((f) => relative(root, f).replaceAll("\\", "/")).join(", ");
|
|
218
|
+
logger?.info(`[rainbowindex] CSS entries: ${list}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
transform(code, id) {
|
|
223
|
+
const file = id.split("?")[0];
|
|
224
|
+
if (CSS_FILE_RE.test(file)) {
|
|
225
|
+
fileVersions.set(file, (fileVersions.get(file) ?? 0) + 1);
|
|
226
|
+
if (hasRIActivation(code)) {
|
|
227
|
+
riCSSFiles.add(file);
|
|
228
|
+
let safe = rewriteDirectiveBodies(code);
|
|
229
|
+
const expandWarnings = [];
|
|
230
|
+
safe = expandApplyGroups(safe, expandWarnings);
|
|
231
|
+
for (const w of expandWarnings) {
|
|
232
|
+
(logger?.warn ?? console.warn)(`[rainbowindex] ${w}`);
|
|
233
|
+
}
|
|
234
|
+
return safe !== code ? safe : null;
|
|
235
|
+
}
|
|
236
|
+
riCSSFiles.delete(file);
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
},
|
|
240
|
+
async handleHotUpdate({ file, server, modules }) {
|
|
241
|
+
if (++hotUpdateCount % PRUNE_INTERVAL === 0) {
|
|
242
|
+
await pruneDeletedFiles();
|
|
243
|
+
}
|
|
244
|
+
if (CSS_FILE_RE.test(file)) {
|
|
245
|
+
await checkCSSFileAsync(file);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!isSourceFile(file)) return;
|
|
249
|
+
const extraModules = [];
|
|
250
|
+
for (const cssFile of riCSSFiles) {
|
|
251
|
+
const mods = server.moduleGraph.getModulesByFile(cssFile);
|
|
252
|
+
if (mods) {
|
|
253
|
+
for (const mod of mods) {
|
|
254
|
+
if (!modules.includes(mod)) {
|
|
255
|
+
extraModules.push(mod);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (extraModules.length > 0) {
|
|
261
|
+
return [...modules, ...extraModules];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
async function pruneDeletedFiles() {
|
|
266
|
+
const tracked = /* @__PURE__ */ new Set([...riCSSFiles, ...fileVersions.keys()]);
|
|
267
|
+
const checks = [...tracked].map(async (file) => {
|
|
268
|
+
try {
|
|
269
|
+
await access(file);
|
|
270
|
+
} catch {
|
|
271
|
+
riCSSFiles.delete(file);
|
|
272
|
+
fileVersions.delete(file);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
await Promise.all(checks);
|
|
276
|
+
}
|
|
277
|
+
async function collectCSSFiles(dir, results) {
|
|
278
|
+
let entries;
|
|
279
|
+
try {
|
|
280
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
281
|
+
} catch (err) {
|
|
282
|
+
if (!isIgnorableDirectoryReadError(err)) {
|
|
283
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
284
|
+
devWarn(`[RI-1601] Failed to scan CSS files in "${dir}": ${msg}`);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
for (const entry of entries) {
|
|
289
|
+
if (entry.isDirectory()) {
|
|
290
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
291
|
+
await collectCSSFiles(join(dir, entry.name), results);
|
|
292
|
+
} else if (entry.isFile() && CSS_FILE_RE.test(entry.name)) {
|
|
293
|
+
results.push(join(dir, entry.name));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function findCSSFilesOnDisk(root2) {
|
|
298
|
+
const results = [];
|
|
299
|
+
await collectCSSFiles(root2, results);
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
async function checkCSSFileAsync(file) {
|
|
303
|
+
const versionBefore = fileVersions.get(file) ?? 0;
|
|
304
|
+
try {
|
|
305
|
+
const raw = await readFile(file, "utf-8");
|
|
306
|
+
if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
|
|
307
|
+
if (hasRIActivation(raw)) {
|
|
308
|
+
riCSSFiles.add(file);
|
|
309
|
+
} else {
|
|
310
|
+
riCSSFiles.delete(file);
|
|
311
|
+
}
|
|
312
|
+
} catch (_err) {
|
|
313
|
+
if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
|
|
314
|
+
riCSSFiles.delete(file);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export {
|
|
319
|
+
rainbowindexVite as default
|
|
320
|
+
};
|