@kbach/react 0.5.23 → 0.5.25

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.
@@ -7,6 +7,13 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.mjs';
7
7
  * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
8
  * Zero setup: just add kbach() to your plugins array.
9
9
  * Only generates CSS for classes you actually use (like Tailwind).
10
+ *
11
+ * How it works (same as Tailwind):
12
+ * 1. Scans your source files at startup for className/kb props
13
+ * 2. Generates CSS rules for only those classes
14
+ * 3. Appends them to your imported CSS file (e.g. app.css) via Vite's transform
15
+ * 4. Vite serves the CSS through its normal pipeline → <link rel="stylesheet">
16
+ * 5. When you add a new class in dev, the CSS file is hot-reloaded
10
17
  */
11
18
 
12
19
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
@@ -7,6 +7,13 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.js';
7
7
  * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
8
  * Zero setup: just add kbach() to your plugins array.
9
9
  * Only generates CSS for classes you actually use (like Tailwind).
10
+ *
11
+ * How it works (same as Tailwind):
12
+ * 1. Scans your source files at startup for className/kb props
13
+ * 2. Generates CSS rules for only those classes
14
+ * 3. Appends them to your imported CSS file (e.g. app.css) via Vite's transform
15
+ * 4. Vite serves the CSS through its normal pipeline → <link rel="stylesheet">
16
+ * 5. When you add a new class in dev, the CSS file is hot-reloaded
10
17
  */
11
18
 
12
19
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
@@ -3350,6 +3350,35 @@ var customVariants = new Proxy(
3350
3350
  // src/vite-plugin.ts
3351
3351
  var VIRTUAL_ID = "virtual:kbach/styles.css";
3352
3352
  var RESOLVED_ID = "\0virtual:kbach/styles.css";
3353
+ var KBACH_START = "/* kbach:start */";
3354
+ var KBACH_END = "/* kbach:end */";
3355
+ function findMainCSSFile(root) {
3356
+ const candidates = [
3357
+ (0, import_path.join)(root, "app", "app.css"),
3358
+ (0, import_path.join)(root, "src", "index.css"),
3359
+ (0, import_path.join)(root, "src", "app.css"),
3360
+ (0, import_path.join)(root, "app", "index.css")
3361
+ ];
3362
+ for (const c of candidates) {
3363
+ if ((0, import_fs.existsSync)(c)) return c;
3364
+ }
3365
+ return null;
3366
+ }
3367
+ function writeKbachToFile(filePath, css) {
3368
+ let existing = "";
3369
+ try {
3370
+ existing = (0, import_fs.readFileSync)(filePath, "utf-8");
3371
+ } catch {
3372
+ }
3373
+ const block = `${KBACH_START}
3374
+ ${css}
3375
+ ${KBACH_END}`;
3376
+ const re = new RegExp(`${KBACH_START}[\\s\\S]*?${KBACH_END}`);
3377
+ const next = existing.includes(KBACH_START) ? existing.replace(re, block) : existing ? `${existing}
3378
+
3379
+ ${block}` : block;
3380
+ if (next !== existing) (0, import_fs.writeFileSync)(filePath, next, "utf-8");
3381
+ }
3353
3382
  function toNumericScreens(screens) {
3354
3383
  const out = {};
3355
3384
  for (const [k, v] of Object.entries(screens)) {
@@ -3397,19 +3426,18 @@ function kbach(userConfig) {
3397
3426
  const tokenCSS = /* @__PURE__ */ new Map();
3398
3427
  let root = process.cwd();
3399
3428
  let server = null;
3400
- function processClassString(classStr) {
3401
- let added = false;
3402
- for (const token of classStr.split(/\s+/).filter(Boolean)) {
3403
- if (tokenCSS.has(token)) continue;
3404
- tokenCSS.set(token, generateClassCSS(token, cfg.theme, cfg.darkMode, screens));
3405
- added = true;
3406
- }
3407
- return added;
3429
+ let mainCSSFile = null;
3430
+ function processToken(token) {
3431
+ if (tokenCSS.has(token)) return false;
3432
+ tokenCSS.set(token, generateClassCSS(token, cfg.theme, cfg.darkMode, screens));
3433
+ return true;
3408
3434
  }
3409
3435
  function extractFromCode(code) {
3410
3436
  let added = false;
3411
3437
  for (const cls of extractClassStrings(code)) {
3412
- if (processClassString(cls)) added = true;
3438
+ for (const token of cls.split(/\s+/).filter(Boolean)) {
3439
+ if (processToken(token)) added = true;
3440
+ }
3413
3441
  }
3414
3442
  return added;
3415
3443
  }
@@ -3422,96 +3450,24 @@ function kbach(userConfig) {
3422
3450
  }
3423
3451
  return parts.join("\n");
3424
3452
  }
3425
- function injectIntoHtml(html) {
3426
- const css = getAllCSS();
3427
- if (!css || html.includes("data-kbach")) return html;
3428
- return html.replace("</head>", `<style data-kbach>${css}</style></head>`);
3429
- }
3430
3453
  return {
3431
3454
  name: "kbach",
3432
3455
  enforce: "pre",
3433
3456
  configResolved(resolved) {
3434
3457
  root = resolved.root;
3435
3458
  },
3459
+ configureServer(s) {
3460
+ server = s;
3461
+ },
3436
3462
  buildStart() {
3437
3463
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
3438
3464
  scanDir((0, import_path.join)(root, dir), (code) => extractFromCode(code));
3439
3465
  }
3466
+ mainCSSFile = findMainCSSFile(root);
3467
+ if (mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
3440
3468
  },
3441
- // ── DEV: intercept every HTML response and inject <style data-kbach> ──────
3442
- // React Router v8 uses chunked streaming SSR — </head> comes in an early
3443
- // res.write() chunk, not res.end(). Buffer all writes, concatenate at end,
3444
- // inject CSS into the full HTML, then send in one go.
3445
- configureServer(s) {
3446
- server = s;
3447
- s.middlewares.use((req, res, next) => {
3448
- const url = req.url ?? "";
3449
- if (/\.(js|ts|jsx|tsx|css|png|jpg|svg|ico|woff2?|ttf|json|map)(\?|$)/.test(url) || url.startsWith("/@") || url.startsWith("/__")) {
3450
- return next();
3451
- }
3452
- const origWrite = res.write.bind(res);
3453
- const origEnd = res.end.bind(res);
3454
- const chunks = [];
3455
- let done = false;
3456
- function toBuffer(chunk, enc) {
3457
- if (chunk == null) return Buffer.alloc(0);
3458
- if (typeof chunk === "string") {
3459
- return Buffer.from(chunk, typeof enc === "string" ? enc : "utf-8");
3460
- }
3461
- if (ArrayBuffer.isView(chunk)) {
3462
- return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
3463
- }
3464
- if (chunk instanceof ArrayBuffer) return Buffer.from(chunk);
3465
- return Buffer.from(String(chunk));
3466
- }
3467
- res.write = function(chunk, enc, cb) {
3468
- if (done) return origWrite(chunk, enc, cb);
3469
- if (chunk != null) chunks.push(toBuffer(chunk, enc));
3470
- if (typeof enc === "function") enc();
3471
- else if (typeof cb === "function") cb();
3472
- return true;
3473
- };
3474
- res.end = function(chunk, enc, cb) {
3475
- done = true;
3476
- if (typeof chunk === "function") {
3477
- cb = chunk;
3478
- chunk = null;
3479
- enc = null;
3480
- } else if (typeof enc === "function") {
3481
- cb = enc;
3482
- enc = null;
3483
- }
3484
- if (chunk != null) chunks.push(toBuffer(chunk, enc));
3485
- if (chunks.length > 0) {
3486
- const html = Buffer.concat(chunks).toString("utf-8");
3487
- if (html.includes("</head>")) {
3488
- const injected = injectIntoHtml(html);
3489
- const buf = Buffer.from(injected, "utf-8");
3490
- try {
3491
- res.removeHeader("transfer-encoding");
3492
- } catch {
3493
- }
3494
- try {
3495
- res.setHeader("content-length", buf.length);
3496
- } catch {
3497
- }
3498
- return origEnd(buf, cb);
3499
- }
3500
- return origEnd(Buffer.concat(chunks), enc, cb);
3501
- }
3502
- return origEnd(void 0, enc, cb);
3503
- };
3504
- next();
3505
- });
3506
- },
3507
- // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
3508
3469
  transform(code, id) {
3509
3470
  const cleanId = id.split("?")[0];
3510
- if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
3511
- const kbachCSS = getAllCSS();
3512
- if (!kbachCSS) return null;
3513
- return { code: code + "\n/* kbach */\n" + kbachCSS, map: null };
3514
- }
3515
3471
  if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
3516
3472
  extractFromCode(code);
3517
3473
  }
@@ -3523,14 +3479,14 @@ function kbach(userConfig) {
3523
3479
  load(id) {
3524
3480
  if (id === RESOLVED_ID) return getAllCSS();
3525
3481
  },
3526
- // ── HMR: full reload when new classes are added ───────────────────────────
3482
+ // ── HMR: write updated CSS to file when new classes are found ────────────
3483
+ // Vite's own file watcher detects the change and hot-reloads the CSS —
3484
+ // no manual invalidation needed.
3527
3485
  handleHotUpdate({ file }) {
3528
3486
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
3529
3487
  try {
3530
3488
  const added = extractFromCode((0, import_fs.readFileSync)(file, "utf-8"));
3531
- if (added && server) {
3532
- (server.hot ?? server.ws)?.send({ type: "full-reload" });
3533
- }
3489
+ if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
3534
3490
  } catch {
3535
3491
  }
3536
3492
  }
@@ -4,10 +4,39 @@ import {
4
4
  } from "./chunk-SOBLKZGE.mjs";
5
5
 
6
6
  // src/vite-plugin.ts
7
- import { readFileSync, readdirSync, statSync } from "fs";
7
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
8
8
  import { join } from "path";
9
9
  var VIRTUAL_ID = "virtual:kbach/styles.css";
10
10
  var RESOLVED_ID = "\0virtual:kbach/styles.css";
11
+ var KBACH_START = "/* kbach:start */";
12
+ var KBACH_END = "/* kbach:end */";
13
+ function findMainCSSFile(root) {
14
+ const candidates = [
15
+ join(root, "app", "app.css"),
16
+ join(root, "src", "index.css"),
17
+ join(root, "src", "app.css"),
18
+ join(root, "app", "index.css")
19
+ ];
20
+ for (const c of candidates) {
21
+ if (existsSync(c)) return c;
22
+ }
23
+ return null;
24
+ }
25
+ function writeKbachToFile(filePath, css) {
26
+ let existing = "";
27
+ try {
28
+ existing = readFileSync(filePath, "utf-8");
29
+ } catch {
30
+ }
31
+ const block = `${KBACH_START}
32
+ ${css}
33
+ ${KBACH_END}`;
34
+ const re = new RegExp(`${KBACH_START}[\\s\\S]*?${KBACH_END}`);
35
+ const next = existing.includes(KBACH_START) ? existing.replace(re, block) : existing ? `${existing}
36
+
37
+ ${block}` : block;
38
+ if (next !== existing) writeFileSync(filePath, next, "utf-8");
39
+ }
11
40
  function toNumericScreens(screens) {
12
41
  const out = {};
13
42
  for (const [k, v] of Object.entries(screens)) {
@@ -55,19 +84,18 @@ function kbach(userConfig) {
55
84
  const tokenCSS = /* @__PURE__ */ new Map();
56
85
  let root = process.cwd();
57
86
  let server = null;
58
- function processClassString(classStr) {
59
- let added = false;
60
- for (const token of classStr.split(/\s+/).filter(Boolean)) {
61
- if (tokenCSS.has(token)) continue;
62
- tokenCSS.set(token, generateClassCSS(token, cfg.theme, cfg.darkMode, screens));
63
- added = true;
64
- }
65
- return added;
87
+ let mainCSSFile = null;
88
+ function processToken(token) {
89
+ if (tokenCSS.has(token)) return false;
90
+ tokenCSS.set(token, generateClassCSS(token, cfg.theme, cfg.darkMode, screens));
91
+ return true;
66
92
  }
67
93
  function extractFromCode(code) {
68
94
  let added = false;
69
95
  for (const cls of extractClassStrings(code)) {
70
- if (processClassString(cls)) added = true;
96
+ for (const token of cls.split(/\s+/).filter(Boolean)) {
97
+ if (processToken(token)) added = true;
98
+ }
71
99
  }
72
100
  return added;
73
101
  }
@@ -80,96 +108,24 @@ function kbach(userConfig) {
80
108
  }
81
109
  return parts.join("\n");
82
110
  }
83
- function injectIntoHtml(html) {
84
- const css = getAllCSS();
85
- if (!css || html.includes("data-kbach")) return html;
86
- return html.replace("</head>", `<style data-kbach>${css}</style></head>`);
87
- }
88
111
  return {
89
112
  name: "kbach",
90
113
  enforce: "pre",
91
114
  configResolved(resolved) {
92
115
  root = resolved.root;
93
116
  },
117
+ configureServer(s) {
118
+ server = s;
119
+ },
94
120
  buildStart() {
95
121
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
96
122
  scanDir(join(root, dir), (code) => extractFromCode(code));
97
123
  }
124
+ mainCSSFile = findMainCSSFile(root);
125
+ if (mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
98
126
  },
99
- // ── DEV: intercept every HTML response and inject <style data-kbach> ──────
100
- // React Router v8 uses chunked streaming SSR — </head> comes in an early
101
- // res.write() chunk, not res.end(). Buffer all writes, concatenate at end,
102
- // inject CSS into the full HTML, then send in one go.
103
- configureServer(s) {
104
- server = s;
105
- s.middlewares.use((req, res, next) => {
106
- const url = req.url ?? "";
107
- if (/\.(js|ts|jsx|tsx|css|png|jpg|svg|ico|woff2?|ttf|json|map)(\?|$)/.test(url) || url.startsWith("/@") || url.startsWith("/__")) {
108
- return next();
109
- }
110
- const origWrite = res.write.bind(res);
111
- const origEnd = res.end.bind(res);
112
- const chunks = [];
113
- let done = false;
114
- function toBuffer(chunk, enc) {
115
- if (chunk == null) return Buffer.alloc(0);
116
- if (typeof chunk === "string") {
117
- return Buffer.from(chunk, typeof enc === "string" ? enc : "utf-8");
118
- }
119
- if (ArrayBuffer.isView(chunk)) {
120
- return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
121
- }
122
- if (chunk instanceof ArrayBuffer) return Buffer.from(chunk);
123
- return Buffer.from(String(chunk));
124
- }
125
- res.write = function(chunk, enc, cb) {
126
- if (done) return origWrite(chunk, enc, cb);
127
- if (chunk != null) chunks.push(toBuffer(chunk, enc));
128
- if (typeof enc === "function") enc();
129
- else if (typeof cb === "function") cb();
130
- return true;
131
- };
132
- res.end = function(chunk, enc, cb) {
133
- done = true;
134
- if (typeof chunk === "function") {
135
- cb = chunk;
136
- chunk = null;
137
- enc = null;
138
- } else if (typeof enc === "function") {
139
- cb = enc;
140
- enc = null;
141
- }
142
- if (chunk != null) chunks.push(toBuffer(chunk, enc));
143
- if (chunks.length > 0) {
144
- const html = Buffer.concat(chunks).toString("utf-8");
145
- if (html.includes("</head>")) {
146
- const injected = injectIntoHtml(html);
147
- const buf = Buffer.from(injected, "utf-8");
148
- try {
149
- res.removeHeader("transfer-encoding");
150
- } catch {
151
- }
152
- try {
153
- res.setHeader("content-length", buf.length);
154
- } catch {
155
- }
156
- return origEnd(buf, cb);
157
- }
158
- return origEnd(Buffer.concat(chunks), enc, cb);
159
- }
160
- return origEnd(void 0, enc, cb);
161
- };
162
- next();
163
- });
164
- },
165
- // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
166
127
  transform(code, id) {
167
128
  const cleanId = id.split("?")[0];
168
- if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
169
- const kbachCSS = getAllCSS();
170
- if (!kbachCSS) return null;
171
- return { code: code + "\n/* kbach */\n" + kbachCSS, map: null };
172
- }
173
129
  if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
174
130
  extractFromCode(code);
175
131
  }
@@ -181,14 +137,14 @@ function kbach(userConfig) {
181
137
  load(id) {
182
138
  if (id === RESOLVED_ID) return getAllCSS();
183
139
  },
184
- // ── HMR: full reload when new classes are added ───────────────────────────
140
+ // ── HMR: write updated CSS to file when new classes are found ────────────
141
+ // Vite's own file watcher detects the change and hot-reloads the CSS —
142
+ // no manual invalidation needed.
185
143
  handleHotUpdate({ file }) {
186
144
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
187
145
  try {
188
146
  const added = extractFromCode(readFileSync(file, "utf-8"));
189
- if (added && server) {
190
- (server.hot ?? server.ws)?.send({ type: "full-reload" });
191
- }
147
+ if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
192
148
  } catch {
193
149
  }
194
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",