@kbach/react 1.0.0-beta.2 → 1.0.0-beta.20
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/README.md +204 -16
- package/dist/chunk-43FOLRQA.mjs +234 -0
- package/dist/{chunk-PAJ6KHDB.mjs → chunk-5EXGGZMA.mjs} +1 -1
- package/dist/{chunk-ZQJKL2MV.mjs → chunk-BJEOK2VK.mjs} +18 -4
- package/dist/chunk-MRBB55KV.mjs +687 -0
- package/dist/{chunk-N5HCQIBB.mjs → chunk-OVKVZA7B.mjs} +21 -1
- package/dist/{config-BQ_b-qxF.d.mts → config-N74_lKa0.d.mts} +33 -2
- package/dist/{config-BQ_b-qxF.d.ts → config-N74_lKa0.d.ts} +33 -2
- package/dist/index.d.mts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +157 -69
- package/dist/index.mjs +55 -106
- package/dist/jsx-dev-runtime.js +30 -4
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +30 -4
- package/dist/jsx-runtime.mjs +3 -3
- package/dist/postcss-plugin.d.mts +36 -0
- package/dist/postcss-plugin.d.ts +36 -0
- package/dist/postcss-plugin.js +1250 -0
- package/dist/postcss-plugin.mjs +84 -0
- package/dist/vite-plugin.d.mts +94 -6
- package/dist/vite-plugin.d.ts +94 -6
- package/dist/vite-plugin.js +495 -910
- package/dist/vite-plugin.mjs +15 -1198
- package/package.json +26 -4
- package/dist/chunk-NKR3PSWO.mjs +0 -115
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveKbachConfig
|
|
3
|
+
} from "./chunk-43FOLRQA.mjs";
|
|
4
|
+
import {
|
|
5
|
+
buildResetCSS,
|
|
6
|
+
defaultTheme
|
|
7
|
+
} from "./chunk-OVKVZA7B.mjs";
|
|
8
|
+
|
|
9
|
+
// src/vite-plugin/index.ts
|
|
10
|
+
import { readFileSync as readFileSync3, writeFileSync, mkdirSync, existsSync, statSync as statSync2 } from "fs";
|
|
11
|
+
import { join as join4, dirname } from "path";
|
|
12
|
+
import { pathToFileURL } from "url";
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
|
|
15
|
+
// src/vite-plugin/cssVars.ts
|
|
16
|
+
function hexToRgbTriplet(hex) {
|
|
17
|
+
const h = hex.replace("#", "");
|
|
18
|
+
const expand = (s) => s.length === 3 ? s.split("").map((c) => c + c).join("") : s;
|
|
19
|
+
const full = expand(h);
|
|
20
|
+
if (full.length !== 6) return null;
|
|
21
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
22
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
23
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
24
|
+
if ([r, g, b].some((n) => Number.isNaN(n))) return null;
|
|
25
|
+
return `${r},${g},${b}`;
|
|
26
|
+
}
|
|
27
|
+
function buildColorVarMap(theme, cssText) {
|
|
28
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
29
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const [name, value] of Object.entries(theme.colors)) {
|
|
31
|
+
if (typeof value !== "string") continue;
|
|
32
|
+
const varName = `--color-${name}`;
|
|
33
|
+
if (cssText.includes(value)) {
|
|
34
|
+
replacements.set(value, `var(${varName})`);
|
|
35
|
+
declarations.set(varName, value);
|
|
36
|
+
}
|
|
37
|
+
const triplet = hexToRgbTriplet(value);
|
|
38
|
+
if (!triplet) continue;
|
|
39
|
+
const rgbaPrefix = `rgba(${triplet},`;
|
|
40
|
+
if (cssText.includes(rgbaPrefix)) {
|
|
41
|
+
const rgbVarName = `--color-${name}-rgb`;
|
|
42
|
+
replacements.set(rgbaPrefix, `rgba(var(${rgbVarName}),`);
|
|
43
|
+
declarations.set(rgbVarName, triplet);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { replacements, declarations };
|
|
47
|
+
}
|
|
48
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
49
|
+
"padding",
|
|
50
|
+
"padding-top",
|
|
51
|
+
"padding-right",
|
|
52
|
+
"padding-bottom",
|
|
53
|
+
"padding-left",
|
|
54
|
+
"margin",
|
|
55
|
+
"margin-top",
|
|
56
|
+
"margin-right",
|
|
57
|
+
"margin-bottom",
|
|
58
|
+
"margin-left",
|
|
59
|
+
"gap",
|
|
60
|
+
"column-gap",
|
|
61
|
+
"row-gap",
|
|
62
|
+
"width",
|
|
63
|
+
"height",
|
|
64
|
+
"min-width",
|
|
65
|
+
"min-height",
|
|
66
|
+
"max-width",
|
|
67
|
+
"max-height",
|
|
68
|
+
"top",
|
|
69
|
+
"right",
|
|
70
|
+
"bottom",
|
|
71
|
+
"left"
|
|
72
|
+
]);
|
|
73
|
+
function buildSpacingVarMap(theme, cssText) {
|
|
74
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
75
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
76
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const [key, val] of Object.entries(theme.spacing)) {
|
|
78
|
+
if (!pxToKey.has(val)) pxToKey.set(val, key);
|
|
79
|
+
}
|
|
80
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
81
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
82
|
+
let m;
|
|
83
|
+
while ((m = declRe.exec(cssText)) !== null) {
|
|
84
|
+
const [, prop, numStr] = m;
|
|
85
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
86
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
87
|
+
if (key === void 0) continue;
|
|
88
|
+
const varName = varNameFor(key);
|
|
89
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
90
|
+
declarations.set(varName, `${numStr}px`);
|
|
91
|
+
}
|
|
92
|
+
return { replacements, declarations };
|
|
93
|
+
}
|
|
94
|
+
function applyVarMap(cssText, ...maps) {
|
|
95
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
96
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const map of maps) {
|
|
98
|
+
for (const [k, v] of map.replacements) replacements.set(k, v);
|
|
99
|
+
for (const [k, v] of map.declarations) declarations.set(k, v);
|
|
100
|
+
}
|
|
101
|
+
const sorted = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
102
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
103
|
+
const patterns = sorted.map(([pattern, replacement]) => [new RegExp(escapeRegex(pattern), "g"), replacement]);
|
|
104
|
+
const text = cssText.replace(/\{([^{}]*)\}/g, (_whole, inner) => {
|
|
105
|
+
let replaced = inner;
|
|
106
|
+
for (const [re, replacement] of patterns) {
|
|
107
|
+
replaced = replaced.replace(re, () => replacement);
|
|
108
|
+
}
|
|
109
|
+
return `{${replaced}}`;
|
|
110
|
+
});
|
|
111
|
+
return { text, declarations };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/vite-plugin/mediaMerge.ts
|
|
115
|
+
var RESPONSIVE_MEDIA_RE = /^@media \(min-width: (\d+)px\) \{ (.+) \}$/;
|
|
116
|
+
function mergeResponsiveMediaBlocks(rules) {
|
|
117
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
118
|
+
const widthOrder = [];
|
|
119
|
+
const unwrapped = [];
|
|
120
|
+
for (const rule of rules) {
|
|
121
|
+
const m = RESPONSIVE_MEDIA_RE.exec(rule);
|
|
122
|
+
if (!m) {
|
|
123
|
+
unwrapped.push(rule);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const width = Number(m[1]);
|
|
127
|
+
if (!byWidth.has(width)) {
|
|
128
|
+
byWidth.set(width, []);
|
|
129
|
+
widthOrder.push(width);
|
|
130
|
+
}
|
|
131
|
+
byWidth.get(width).push(m[2]);
|
|
132
|
+
}
|
|
133
|
+
widthOrder.sort((a, b) => a - b);
|
|
134
|
+
const merged = widthOrder.map((w) => `@media (min-width: ${w}px) { ${byWidth.get(w).join(" ")} }`);
|
|
135
|
+
return [...unwrapped, ...merged];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/vite-plugin/prettify.ts
|
|
139
|
+
function prettifyCSS(css) {
|
|
140
|
+
const out = [];
|
|
141
|
+
let depth = 0;
|
|
142
|
+
let current = "";
|
|
143
|
+
for (const ch of css) {
|
|
144
|
+
if (ch === "{") {
|
|
145
|
+
const selector = current.trim();
|
|
146
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
147
|
+
depth++;
|
|
148
|
+
current = "";
|
|
149
|
+
} else if (ch === "}") {
|
|
150
|
+
const decl = current.trim();
|
|
151
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
152
|
+
depth = Math.max(0, depth - 1);
|
|
153
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
154
|
+
current = "";
|
|
155
|
+
} else if (ch === ";") {
|
|
156
|
+
const decl = current.trim();
|
|
157
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
158
|
+
current = "";
|
|
159
|
+
} else {
|
|
160
|
+
current += ch;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const trailing = current.trim();
|
|
164
|
+
if (trailing) out.push(trailing);
|
|
165
|
+
return out.join("\n");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/vite-plugin/format.ts
|
|
169
|
+
var KBACH_START = "/* kbach:start */";
|
|
170
|
+
var KBACH_END = "/* kbach:end */";
|
|
171
|
+
function formatKbachCSS(tokenRules, theme, usedTags) {
|
|
172
|
+
const allEntries = [];
|
|
173
|
+
for (const rules of tokenRules.values()) {
|
|
174
|
+
for (const entry of rules) allEntries.push(entry);
|
|
175
|
+
}
|
|
176
|
+
allEntries.sort((a, b) => a.order - b.order);
|
|
177
|
+
const allRules = allEntries.map((e) => e.rule);
|
|
178
|
+
const merged = mergeResponsiveMediaBlocks(allRules);
|
|
179
|
+
const compact = merged.join("\n");
|
|
180
|
+
const colorMap = buildColorVarMap(theme, compact);
|
|
181
|
+
const spacingMap = buildSpacingVarMap(theme, compact);
|
|
182
|
+
const { text, declarations } = applyVarMap(compact, colorMap, spacingMap);
|
|
183
|
+
const lines = ["/* Generated by Kbach \u2014 do not edit */", "", buildResetCSS(usedTags)];
|
|
184
|
+
lines.push("", ":root {", ` --kb-dark-mode: ${theme.darkMode};`);
|
|
185
|
+
for (const [varName, val] of declarations) lines.push(` ${varName}: ${val};`);
|
|
186
|
+
lines.push("}");
|
|
187
|
+
lines.push("", prettifyCSS(text));
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
function writeKbachToFile(filePath, css, readFile, writeFile) {
|
|
191
|
+
let existing = "";
|
|
192
|
+
try {
|
|
193
|
+
existing = readFile(filePath);
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
const block = `${KBACH_START}
|
|
197
|
+
${css}
|
|
198
|
+
${KBACH_END}`;
|
|
199
|
+
const start = existing.indexOf(KBACH_START);
|
|
200
|
+
const end = existing.indexOf(KBACH_END);
|
|
201
|
+
const next = start !== -1 && end !== -1 && end > start ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
|
|
202
|
+
|
|
203
|
+
${block}` : block;
|
|
204
|
+
if (next === existing) return false;
|
|
205
|
+
writeFile(filePath, next);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/staticCss/engine.ts
|
|
210
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
211
|
+
import { join as join3 } from "path";
|
|
212
|
+
|
|
213
|
+
// src/vite-plugin/wasmNode.ts
|
|
214
|
+
import { generate_css } from "@kbach/core-engine/node";
|
|
215
|
+
function generateCssForToken(token, themeJson) {
|
|
216
|
+
return JSON.parse(generate_css(token, themeJson));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/vite-plugin/scan.ts
|
|
220
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
221
|
+
import { join } from "path";
|
|
222
|
+
function stripComments(code) {
|
|
223
|
+
let out = "";
|
|
224
|
+
let i = 0;
|
|
225
|
+
const n = code.length;
|
|
226
|
+
while (i < n) {
|
|
227
|
+
const two = code[i] + (code[i + 1] ?? "");
|
|
228
|
+
if (two === "//") {
|
|
229
|
+
i += 2;
|
|
230
|
+
while (i < n && code[i] !== "\n") i++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (two === "/*") {
|
|
234
|
+
i += 2;
|
|
235
|
+
while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
|
|
236
|
+
i += 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const ch = code[i];
|
|
240
|
+
if (ch === '"' || ch === "'") {
|
|
241
|
+
out += ch;
|
|
242
|
+
i++;
|
|
243
|
+
while (i < n && code[i] !== ch) {
|
|
244
|
+
if (code[i] === "\\") {
|
|
245
|
+
out += code[i] + (code[i + 1] ?? "");
|
|
246
|
+
i += 2;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
out += code[i];
|
|
250
|
+
i++;
|
|
251
|
+
}
|
|
252
|
+
out += code[i] ?? "";
|
|
253
|
+
i++;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === "`") {
|
|
257
|
+
out += ch;
|
|
258
|
+
i++;
|
|
259
|
+
let interpolationDepth = 0;
|
|
260
|
+
while (i < n) {
|
|
261
|
+
if (interpolationDepth === 0 && code[i] === "`") {
|
|
262
|
+
out += code[i];
|
|
263
|
+
i++;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
if (interpolationDepth === 0 && code[i] === "\\") {
|
|
267
|
+
out += code[i] + (code[i + 1] ?? "");
|
|
268
|
+
i += 2;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
|
|
272
|
+
out += "${";
|
|
273
|
+
i += 2;
|
|
274
|
+
interpolationDepth = 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (interpolationDepth > 0) {
|
|
278
|
+
if (code[i] === "{") interpolationDepth++;
|
|
279
|
+
else if (code[i] === "}") {
|
|
280
|
+
interpolationDepth--;
|
|
281
|
+
if (interpolationDepth === 0) {
|
|
282
|
+
out += "}";
|
|
283
|
+
i++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
out += code[i];
|
|
289
|
+
i++;
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
out += ch;
|
|
294
|
+
i++;
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function splitClassTokens(str) {
|
|
299
|
+
return str.split(/\s+/).filter(Boolean);
|
|
300
|
+
}
|
|
301
|
+
var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
|
|
302
|
+
function pushTokens(str, into) {
|
|
303
|
+
for (const tok of splitClassTokens(str)) {
|
|
304
|
+
if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function pushTemplateLiteralBody(body, into) {
|
|
308
|
+
const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
|
|
309
|
+
let im;
|
|
310
|
+
while ((im = interpolationRe.exec(body)) !== null) {
|
|
311
|
+
pushClassLikeStrings(im[0], into);
|
|
312
|
+
}
|
|
313
|
+
pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, () => "$"), into);
|
|
314
|
+
}
|
|
315
|
+
function pushClassLikeStrings(text, into) {
|
|
316
|
+
const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
|
|
317
|
+
let qm;
|
|
318
|
+
while ((qm = quotedStringRe.exec(text)) !== null) {
|
|
319
|
+
const [, quote, content] = qm;
|
|
320
|
+
if (quote === "`") pushTemplateLiteralBody(content, into);
|
|
321
|
+
else pushTokens(content, into);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
|
|
325
|
+
function scanUsedTags(code) {
|
|
326
|
+
const cleaned = stripComments(code);
|
|
327
|
+
const tags = /* @__PURE__ */ new Set();
|
|
328
|
+
let m;
|
|
329
|
+
JSX_TAG_RE.lastIndex = 0;
|
|
330
|
+
while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
|
|
331
|
+
return tags;
|
|
332
|
+
}
|
|
333
|
+
function extractClassStrings(rawCode) {
|
|
334
|
+
const code = stripComments(rawCode);
|
|
335
|
+
const found = /* @__PURE__ */ new Set();
|
|
336
|
+
const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
|
|
337
|
+
let m;
|
|
338
|
+
while ((m = simpleRe.exec(code)) !== null) pushTokens(m[2], found);
|
|
339
|
+
const jsxExprRe = /(?:className|kb)=\{/g;
|
|
340
|
+
while ((m = jsxExprRe.exec(code)) !== null) {
|
|
341
|
+
let depth = 1;
|
|
342
|
+
let i = m.index + m[0].length;
|
|
343
|
+
let block = "";
|
|
344
|
+
while (i < code.length && depth > 0) {
|
|
345
|
+
const ch = code[i];
|
|
346
|
+
if (ch === "{") depth++;
|
|
347
|
+
else if (ch === "}") {
|
|
348
|
+
if (--depth === 0) break;
|
|
349
|
+
}
|
|
350
|
+
block += ch;
|
|
351
|
+
i++;
|
|
352
|
+
}
|
|
353
|
+
jsxExprRe.lastIndex = i + 1;
|
|
354
|
+
pushClassLikeStrings(block, found);
|
|
355
|
+
}
|
|
356
|
+
const classComposerCallRe = /(?:clsx|cn|classnames|cx|kb)\(/g;
|
|
357
|
+
while ((m = classComposerCallRe.exec(code)) !== null) {
|
|
358
|
+
let depth = 1;
|
|
359
|
+
let i = m.index + m[0].length;
|
|
360
|
+
let block = "";
|
|
361
|
+
while (i < code.length && depth > 0) {
|
|
362
|
+
const ch = code[i];
|
|
363
|
+
if (ch === "(") depth++;
|
|
364
|
+
else if (ch === ")") {
|
|
365
|
+
if (--depth === 0) break;
|
|
366
|
+
}
|
|
367
|
+
block += ch;
|
|
368
|
+
i++;
|
|
369
|
+
}
|
|
370
|
+
classComposerCallRe.lastIndex = i + 1;
|
|
371
|
+
pushClassLikeStrings(block, found);
|
|
372
|
+
}
|
|
373
|
+
const templateRe = /`([^`]{1,2000})`/g;
|
|
374
|
+
while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
|
|
375
|
+
return [...found];
|
|
376
|
+
}
|
|
377
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".output", ".git", ".svn", ".cache", "coverage"]);
|
|
378
|
+
var MAX_SCAN_DEPTH = 10;
|
|
379
|
+
var DEFAULT_FILE_MATCH = /\.(tsx?|jsx?)$/;
|
|
380
|
+
function scanDir(dir, onFile, depth = 0, fileMatch = DEFAULT_FILE_MATCH) {
|
|
381
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
382
|
+
let entries;
|
|
383
|
+
try {
|
|
384
|
+
entries = readdirSync(dir);
|
|
385
|
+
} catch {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
|
|
390
|
+
const full = join(dir, entry);
|
|
391
|
+
try {
|
|
392
|
+
const st = statSync(full);
|
|
393
|
+
if (st.isDirectory()) scanDir(full, onFile, depth + 1, fileMatch);
|
|
394
|
+
else if (fileMatch.test(entry)) onFile(full, readFileSync(full, "utf-8"));
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/vite-plugin/unknownClassWarnings.ts
|
|
401
|
+
import { join as join2 } from "path";
|
|
402
|
+
|
|
403
|
+
// src/terminalWarn.ts
|
|
404
|
+
var useColor = !!process.stdout?.isTTY && !process.env.NO_COLOR;
|
|
405
|
+
var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
406
|
+
var green = (s) => paint("32", s);
|
|
407
|
+
var blue = (s) => paint("34", s);
|
|
408
|
+
var white = (s) => paint("97", s);
|
|
409
|
+
var gray = (s) => paint("90", s);
|
|
410
|
+
var bold = (s) => paint("1", s);
|
|
411
|
+
var highlight = (s) => bold(white(s));
|
|
412
|
+
function kbachTag() {
|
|
413
|
+
return `${green("\u2192")} ${bold(green("[Kbach]"))}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/vite-plugin/unknownClassWarnings.ts
|
|
417
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
418
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
419
|
+
function findLineCol(code, needle) {
|
|
420
|
+
const index = code.indexOf(needle);
|
|
421
|
+
if (index === -1) return null;
|
|
422
|
+
let line = 1;
|
|
423
|
+
let lastNewline = -1;
|
|
424
|
+
for (let i = 0; i < index; i++) {
|
|
425
|
+
if (code.charCodeAt(i) === 10) {
|
|
426
|
+
line++;
|
|
427
|
+
lastNewline = i;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return { line, column: index - lastNewline };
|
|
431
|
+
}
|
|
432
|
+
function scanCssFileInto(code, into) {
|
|
433
|
+
const re = new RegExp(CSS_CLASS_SELECTOR_RE.source, "g");
|
|
434
|
+
let m;
|
|
435
|
+
while ((m = re.exec(code)) !== null) into.add(m[1]);
|
|
436
|
+
}
|
|
437
|
+
function scanProjectCssSelectors(root, includeDirs) {
|
|
438
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const dir of includeDirs) {
|
|
440
|
+
scanDir(join2(root, dir), (_filePath, code) => scanCssFileInto(code, projectCssClasses), 0, CSS_FILE_RE);
|
|
441
|
+
}
|
|
442
|
+
return projectCssClasses;
|
|
443
|
+
}
|
|
444
|
+
function stripModifierPrefix(token) {
|
|
445
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
446
|
+
}
|
|
447
|
+
var MARKER_CLASSES = /* @__PURE__ */ new Set(["group", "peer"]);
|
|
448
|
+
function isMarkerClass(base) {
|
|
449
|
+
if (MARKER_CLASSES.has(base)) return true;
|
|
450
|
+
const slashIdx = base.lastIndexOf("/");
|
|
451
|
+
if (slashIdx === -1) return false;
|
|
452
|
+
return MARKER_CLASSES.has(base.slice(0, slashIdx)) && slashIdx < base.length - 1;
|
|
453
|
+
}
|
|
454
|
+
function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
|
|
455
|
+
if (isResolvable || warnedTokens.has(token)) return;
|
|
456
|
+
const base = stripModifierPrefix(token);
|
|
457
|
+
if (!base || projectCssClasses.has(base) || isMarkerClass(base)) return;
|
|
458
|
+
warnedTokens.add(token);
|
|
459
|
+
const pos = findLineCol(code, token);
|
|
460
|
+
const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
|
|
461
|
+
console.warn(
|
|
462
|
+
`${kbachTag()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
|
|
463
|
+
${bold("at")} ${location}`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/staticCss/engine.ts
|
|
468
|
+
function normPath(p) {
|
|
469
|
+
const fwd = p.replace(/\\/g, "/");
|
|
470
|
+
return process.platform === "win32" ? fwd.toLowerCase() : fwd;
|
|
471
|
+
}
|
|
472
|
+
var SAFELIST_KEY = "\0kbach-safelist";
|
|
473
|
+
function createKbachStaticCssEngine(options) {
|
|
474
|
+
const { root, theme, themeJson, includeDirs, safelist } = options;
|
|
475
|
+
const fileTokens = /* @__PURE__ */ new Map();
|
|
476
|
+
const tokenRules = /* @__PURE__ */ new Map();
|
|
477
|
+
const fileTags = /* @__PURE__ */ new Map();
|
|
478
|
+
let projectCssClasses = /* @__PURE__ */ new Set();
|
|
479
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
480
|
+
function resolveToken(tok) {
|
|
481
|
+
if (!tokenRules.has(tok)) {
|
|
482
|
+
tokenRules.set(tok, generateCssForToken(tok, themeJson).rules);
|
|
483
|
+
}
|
|
484
|
+
return tokenRules.get(tok);
|
|
485
|
+
}
|
|
486
|
+
function processFile(filePath, code) {
|
|
487
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
488
|
+
for (const tok of extractClassStrings(code)) {
|
|
489
|
+
tokens.add(tok);
|
|
490
|
+
const rules = resolveToken(tok);
|
|
491
|
+
if (process.env.NODE_ENV !== "production") {
|
|
492
|
+
warnIfUnknownClass(tok, filePath, code, rules.length > 0, projectCssClasses, warnedTokens);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
fileTokens.set(normPath(filePath), tokens);
|
|
496
|
+
fileTags.set(normPath(filePath), scanUsedTags(code));
|
|
497
|
+
}
|
|
498
|
+
function removeFile(filePath) {
|
|
499
|
+
fileTokens.delete(normPath(filePath));
|
|
500
|
+
fileTags.delete(normPath(filePath));
|
|
501
|
+
}
|
|
502
|
+
function processSafelist() {
|
|
503
|
+
if (safelist.length === 0) return;
|
|
504
|
+
const tokens = new Set(safelist);
|
|
505
|
+
for (const tok of tokens) resolveToken(tok);
|
|
506
|
+
fileTokens.set(SAFELIST_KEY, tokens);
|
|
507
|
+
}
|
|
508
|
+
function activeTokens() {
|
|
509
|
+
const active = /* @__PURE__ */ new Set();
|
|
510
|
+
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
511
|
+
return active;
|
|
512
|
+
}
|
|
513
|
+
function activeTags() {
|
|
514
|
+
const active = /* @__PURE__ */ new Set();
|
|
515
|
+
for (const tags of fileTags.values()) for (const t of tags) active.add(t);
|
|
516
|
+
return active;
|
|
517
|
+
}
|
|
518
|
+
function generateCSS() {
|
|
519
|
+
const active = activeTokens();
|
|
520
|
+
const view = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const tok of active) {
|
|
522
|
+
const rules = tokenRules.get(tok);
|
|
523
|
+
if (rules) view.set(tok, rules);
|
|
524
|
+
}
|
|
525
|
+
for (const tok of tokenRules.keys()) {
|
|
526
|
+
if (!active.has(tok)) tokenRules.delete(tok);
|
|
527
|
+
}
|
|
528
|
+
return formatKbachCSS(view, theme, activeTags());
|
|
529
|
+
}
|
|
530
|
+
function initialScan() {
|
|
531
|
+
for (const dir of includeDirs) {
|
|
532
|
+
scanDir(join3(root, dir), processFile);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function scanProjectCssSelectorsOnce() {
|
|
536
|
+
projectCssClasses = scanProjectCssSelectors(root, includeDirs);
|
|
537
|
+
}
|
|
538
|
+
return { processFile, removeFile, initialScan, processSafelist, generateCSS, scanProjectCssSelectorsOnce };
|
|
539
|
+
}
|
|
540
|
+
function readSourceFile(path) {
|
|
541
|
+
return readFileSync2(path, "utf-8");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/vite-plugin/index.ts
|
|
545
|
+
function findMainCSSFile(root, includeDirs, explicitCssFile) {
|
|
546
|
+
if (explicitCssFile) return join4(root, explicitCssFile);
|
|
547
|
+
const searchDirs = [...includeDirs, "."];
|
|
548
|
+
for (const dir of searchDirs) {
|
|
549
|
+
const candidate = join4(root, dir, "kbach.css");
|
|
550
|
+
if (!existsSync(candidate)) continue;
|
|
551
|
+
try {
|
|
552
|
+
const content = readFileSync3(candidate, "utf-8");
|
|
553
|
+
if (content.includes(KBACH_START) && content.includes(KBACH_END)) return candidate;
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
for (const dir of includeDirs) {
|
|
558
|
+
const full = join4(root, dir);
|
|
559
|
+
try {
|
|
560
|
+
if (statSync2(full).isDirectory()) return join4(full, "kbach.css");
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return join4(root, includeDirs[0] ?? "src", "kbach.css");
|
|
565
|
+
}
|
|
566
|
+
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
|
|
567
|
+
var CSS_SYNC_DEBOUNCE_MS = 50;
|
|
568
|
+
function writeFileEnsuringDir(path, content) {
|
|
569
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
570
|
+
writeFileSync(path, content, "utf-8");
|
|
571
|
+
}
|
|
572
|
+
function isEsmRequireFailure(err) {
|
|
573
|
+
if (!(err instanceof Error)) return false;
|
|
574
|
+
return err.code === "ERR_REQUIRE_ESM" || /is not defined in ES module scope/.test(err.message);
|
|
575
|
+
}
|
|
576
|
+
async function importConfigModule(path) {
|
|
577
|
+
const mod = await import(pathToFileURL(path).href);
|
|
578
|
+
return mod.default ?? mod;
|
|
579
|
+
}
|
|
580
|
+
function requireConfigModule(root, path) {
|
|
581
|
+
const mod = createRequire(root)(path);
|
|
582
|
+
return mod.default ?? mod;
|
|
583
|
+
}
|
|
584
|
+
async function loadConfigFile(root) {
|
|
585
|
+
const cjsPath = join4(root, "kbach.config.cjs");
|
|
586
|
+
if (existsSync(cjsPath)) return requireConfigModule(root, cjsPath);
|
|
587
|
+
const mjsPath = join4(root, "kbach.config.mjs");
|
|
588
|
+
if (existsSync(mjsPath)) return importConfigModule(mjsPath);
|
|
589
|
+
const jsPath = join4(root, "kbach.config.js");
|
|
590
|
+
if (!existsSync(jsPath)) return void 0;
|
|
591
|
+
try {
|
|
592
|
+
return requireConfigModule(root, jsPath);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
if (!isEsmRequireFailure(err)) throw err;
|
|
595
|
+
return importConfigModule(jsPath);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
async function resolveEffectiveTheme(root, options) {
|
|
599
|
+
if (options.theme) return options.theme;
|
|
600
|
+
if (options.config) return resolveKbachConfig(options.config);
|
|
601
|
+
const discovered = await loadConfigFile(root);
|
|
602
|
+
return discovered ? resolveKbachConfig(discovered) : defaultTheme;
|
|
603
|
+
}
|
|
604
|
+
function kbach(options = {}) {
|
|
605
|
+
const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
|
|
606
|
+
const safelist = options.safelist ?? [];
|
|
607
|
+
let root = process.cwd();
|
|
608
|
+
let theme = defaultTheme;
|
|
609
|
+
let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
610
|
+
let mainCSSFilePath = join4(root, "src", "kbach.css");
|
|
611
|
+
function mainCSSFile() {
|
|
612
|
+
return mainCSSFilePath;
|
|
613
|
+
}
|
|
614
|
+
function syncMainCSSFile(server) {
|
|
615
|
+
const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
616
|
+
if (changed && server) server.watcher.emit("change", mainCSSFile());
|
|
617
|
+
}
|
|
618
|
+
let syncTimer = null;
|
|
619
|
+
function scheduleSync(server) {
|
|
620
|
+
if (syncTimer) clearTimeout(syncTimer);
|
|
621
|
+
syncTimer = setTimeout(() => {
|
|
622
|
+
syncTimer = null;
|
|
623
|
+
syncMainCSSFile(server);
|
|
624
|
+
}, CSS_SYNC_DEBOUNCE_MS);
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
name: "kbach",
|
|
628
|
+
enforce: "pre",
|
|
629
|
+
async configResolved(resolved) {
|
|
630
|
+
root = resolved.root;
|
|
631
|
+
theme = await resolveEffectiveTheme(root, options);
|
|
632
|
+
engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
633
|
+
mainCSSFilePath = findMainCSSFile(root, includeDirs, options.cssFile);
|
|
634
|
+
},
|
|
635
|
+
buildStart() {
|
|
636
|
+
engine.scanProjectCssSelectorsOnce();
|
|
637
|
+
engine.initialScan();
|
|
638
|
+
engine.processSafelist();
|
|
639
|
+
writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
640
|
+
},
|
|
641
|
+
// handleHotUpdate covers edits; add/unlink (create/delete) land on the
|
|
642
|
+
// raw watcher instead — Vite's handleHotUpdate hook only fires for
|
|
643
|
+
// type === "update".
|
|
644
|
+
configureServer(server) {
|
|
645
|
+
const onAddOrUnlink = (file, isUnlink) => {
|
|
646
|
+
if (file.includes("node_modules")) return;
|
|
647
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
648
|
+
if (isUnlink) {
|
|
649
|
+
engine.removeFile(file);
|
|
650
|
+
} else {
|
|
651
|
+
try {
|
|
652
|
+
engine.processFile(file, readSourceFile(file));
|
|
653
|
+
} catch {
|
|
654
|
+
engine.removeFile(file);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
scheduleSync(server);
|
|
658
|
+
};
|
|
659
|
+
server.watcher.on("add", (file) => onAddOrUnlink(file, false));
|
|
660
|
+
server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
|
|
661
|
+
},
|
|
662
|
+
handleHotUpdate({ file, server }) {
|
|
663
|
+
if (file.includes("node_modules")) return;
|
|
664
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
665
|
+
try {
|
|
666
|
+
engine.processFile(file, readSourceFile(file));
|
|
667
|
+
} catch {
|
|
668
|
+
engine.removeFile(file);
|
|
669
|
+
}
|
|
670
|
+
scheduleSync(server);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export {
|
|
676
|
+
gray,
|
|
677
|
+
bold,
|
|
678
|
+
highlight,
|
|
679
|
+
kbachTag,
|
|
680
|
+
createKbachStaticCssEngine,
|
|
681
|
+
findMainCSSFile,
|
|
682
|
+
DEFAULT_SCAN_DIRS,
|
|
683
|
+
CSS_SYNC_DEBOUNCE_MS,
|
|
684
|
+
loadConfigFile,
|
|
685
|
+
resolveEffectiveTheme,
|
|
686
|
+
kbach
|
|
687
|
+
};
|