@kbach/react 0.5.19 → 0.5.21

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.
@@ -4,15 +4,9 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.mjs';
4
4
  /**
5
5
  * @kbach/react/vite
6
6
  *
7
- * Vite plugin — scans your source files for Kbach className/kb props and
8
- * appends the generated CSS to any CSS file already imported in your project.
9
- *
10
- * Zero setup beyond adding the plugin to vite.config.ts:
11
- *
12
- * import { kbach } from '@kbach/react/vite';
13
- * plugins: [kbach()]
14
- *
15
- * CSS is only generated for the classes you actually use (like Tailwind).
7
+ * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
+ * Zero setup: just add kbach() to your plugins array.
9
+ * Only generates CSS for classes you actually use (like Tailwind).
16
10
  */
17
11
 
18
12
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
@@ -4,15 +4,9 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.js';
4
4
  /**
5
5
  * @kbach/react/vite
6
6
  *
7
- * Vite plugin — scans your source files for Kbach className/kb props and
8
- * appends the generated CSS to any CSS file already imported in your project.
9
- *
10
- * Zero setup beyond adding the plugin to vite.config.ts:
11
- *
12
- * import { kbach } from '@kbach/react/vite';
13
- * plugins: [kbach()]
14
- *
15
- * CSS is only generated for the classes you actually use (like Tailwind).
7
+ * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
+ * Zero setup: just add kbach() to your plugins array.
9
+ * Only generates CSS for classes you actually use (like Tailwind).
16
10
  */
17
11
 
18
12
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
@@ -3395,7 +3395,6 @@ function kbach(userConfig) {
3395
3395
  const cfg = buildConfig(userConfig ?? {});
3396
3396
  const screens = toNumericScreens(cfg.theme.screens ?? {});
3397
3397
  const tokenCSS = /* @__PURE__ */ new Map();
3398
- const processedCSSFiles = /* @__PURE__ */ new Set();
3399
3398
  let root = process.cwd();
3400
3399
  let server = null;
3401
3400
  function processClassString(classStr) {
@@ -3423,12 +3422,10 @@ function kbach(userConfig) {
3423
3422
  }
3424
3423
  return parts.join("\n");
3425
3424
  }
3426
- function invalidateCSSModules() {
3427
- if (!server) return;
3428
- for (const file of processedCSSFiles) {
3429
- const mod = server.moduleGraph.getModuleByFile(file);
3430
- if (mod) server.moduleGraph.invalidateModule(mod);
3431
- }
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>`);
3432
3429
  }
3433
3430
  return {
3434
3431
  name: "kbach",
@@ -3436,40 +3433,68 @@ function kbach(userConfig) {
3436
3433
  configResolved(resolved) {
3437
3434
  root = resolved.root;
3438
3435
  },
3439
- configureServer(s) {
3440
- server = s;
3441
- },
3442
3436
  buildStart() {
3443
3437
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
3444
3438
  scanDir((0, import_path.join)(root, dir), (code) => extractFromCode(code));
3445
3439
  }
3446
3440
  },
3447
- resolveId(id) {
3448
- if (id === VIRTUAL_ID) return RESOLVED_ID;
3449
- },
3450
- load(id) {
3451
- if (id === RESOLVED_ID) return getAllCSS();
3441
+ // ── DEV: intercept every HTTP response and inject <style data-kbach> ──────
3442
+ // React Router v8's SSR middleware generates HTML without going through
3443
+ // transformIndexHtml, so we intercept at the Node.js response level.
3444
+ configureServer(s) {
3445
+ server = s;
3446
+ s.middlewares.use((_req, res, next) => {
3447
+ const origEnd = res.end.bind(res);
3448
+ res.end = function(chunk, encoding, cb) {
3449
+ try {
3450
+ if (chunk != null) {
3451
+ const str = Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : chunk;
3452
+ if (typeof str === "string" && str.includes("</head>")) {
3453
+ const injected = injectIntoHtml(str);
3454
+ if (injected !== str) {
3455
+ chunk = Buffer.isBuffer(chunk) ? Buffer.from(injected, "utf-8") : injected;
3456
+ if (res.hasHeader("content-length")) {
3457
+ res.setHeader("content-length", Buffer.byteLength(
3458
+ Buffer.isBuffer(chunk) ? chunk : String(chunk)
3459
+ ));
3460
+ }
3461
+ }
3462
+ }
3463
+ }
3464
+ } catch {
3465
+ }
3466
+ return origEnd(chunk, encoding, cb);
3467
+ };
3468
+ next();
3469
+ });
3452
3470
  },
3471
+ // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
3453
3472
  transform(code, id) {
3454
3473
  const cleanId = id.split("?")[0];
3455
3474
  if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
3456
- processedCSSFiles.add(cleanId);
3457
3475
  const kbachCSS = getAllCSS();
3458
3476
  if (!kbachCSS) return null;
3459
- return { code: code + "\n/* kbach utilities */\n" + kbachCSS, map: null };
3477
+ return { code: code + "\n/* kbach */\n" + kbachCSS, map: null };
3460
3478
  }
3461
- if (/\.(tsx?|jsx?)$/.test(id) && !id.includes("node_modules")) {
3462
- const added = extractFromCode(code);
3463
- if (added && server && processedCSSFiles.size > 0) {
3464
- setTimeout(invalidateCSSModules, 0);
3465
- }
3479
+ if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
3480
+ extractFromCode(code);
3466
3481
  }
3467
3482
  },
3483
+ // ── Virtual module (opt-in explicit import) ───────────────────────────────
3484
+ resolveId(id) {
3485
+ if (id === VIRTUAL_ID) return RESOLVED_ID;
3486
+ },
3487
+ load(id) {
3488
+ if (id === RESOLVED_ID) return getAllCSS();
3489
+ },
3490
+ // ── HMR: full reload when new classes are added ───────────────────────────
3468
3491
  handleHotUpdate({ file }) {
3469
3492
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
3470
3493
  try {
3471
3494
  const added = extractFromCode((0, import_fs.readFileSync)(file, "utf-8"));
3472
- if (added) invalidateCSSModules();
3495
+ if (added && server) {
3496
+ (server.hot ?? server.ws)?.send({ type: "full-reload" });
3497
+ }
3473
3498
  } catch {
3474
3499
  }
3475
3500
  }
@@ -53,7 +53,6 @@ function kbach(userConfig) {
53
53
  const cfg = buildConfig(userConfig ?? {});
54
54
  const screens = toNumericScreens(cfg.theme.screens ?? {});
55
55
  const tokenCSS = /* @__PURE__ */ new Map();
56
- const processedCSSFiles = /* @__PURE__ */ new Set();
57
56
  let root = process.cwd();
58
57
  let server = null;
59
58
  function processClassString(classStr) {
@@ -81,12 +80,10 @@ function kbach(userConfig) {
81
80
  }
82
81
  return parts.join("\n");
83
82
  }
84
- function invalidateCSSModules() {
85
- if (!server) return;
86
- for (const file of processedCSSFiles) {
87
- const mod = server.moduleGraph.getModuleByFile(file);
88
- if (mod) server.moduleGraph.invalidateModule(mod);
89
- }
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>`);
90
87
  }
91
88
  return {
92
89
  name: "kbach",
@@ -94,40 +91,68 @@ function kbach(userConfig) {
94
91
  configResolved(resolved) {
95
92
  root = resolved.root;
96
93
  },
97
- configureServer(s) {
98
- server = s;
99
- },
100
94
  buildStart() {
101
95
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
102
96
  scanDir(join(root, dir), (code) => extractFromCode(code));
103
97
  }
104
98
  },
105
- resolveId(id) {
106
- if (id === VIRTUAL_ID) return RESOLVED_ID;
107
- },
108
- load(id) {
109
- if (id === RESOLVED_ID) return getAllCSS();
99
+ // ── DEV: intercept every HTTP response and inject <style data-kbach> ──────
100
+ // React Router v8's SSR middleware generates HTML without going through
101
+ // transformIndexHtml, so we intercept at the Node.js response level.
102
+ configureServer(s) {
103
+ server = s;
104
+ s.middlewares.use((_req, res, next) => {
105
+ const origEnd = res.end.bind(res);
106
+ res.end = function(chunk, encoding, cb) {
107
+ try {
108
+ if (chunk != null) {
109
+ const str = Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : chunk;
110
+ if (typeof str === "string" && str.includes("</head>")) {
111
+ const injected = injectIntoHtml(str);
112
+ if (injected !== str) {
113
+ chunk = Buffer.isBuffer(chunk) ? Buffer.from(injected, "utf-8") : injected;
114
+ if (res.hasHeader("content-length")) {
115
+ res.setHeader("content-length", Buffer.byteLength(
116
+ Buffer.isBuffer(chunk) ? chunk : String(chunk)
117
+ ));
118
+ }
119
+ }
120
+ }
121
+ }
122
+ } catch {
123
+ }
124
+ return origEnd(chunk, encoding, cb);
125
+ };
126
+ next();
127
+ });
110
128
  },
129
+ // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
111
130
  transform(code, id) {
112
131
  const cleanId = id.split("?")[0];
113
132
  if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
114
- processedCSSFiles.add(cleanId);
115
133
  const kbachCSS = getAllCSS();
116
134
  if (!kbachCSS) return null;
117
- return { code: code + "\n/* kbach utilities */\n" + kbachCSS, map: null };
135
+ return { code: code + "\n/* kbach */\n" + kbachCSS, map: null };
118
136
  }
119
- if (/\.(tsx?|jsx?)$/.test(id) && !id.includes("node_modules")) {
120
- const added = extractFromCode(code);
121
- if (added && server && processedCSSFiles.size > 0) {
122
- setTimeout(invalidateCSSModules, 0);
123
- }
137
+ if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
138
+ extractFromCode(code);
124
139
  }
125
140
  },
141
+ // ── Virtual module (opt-in explicit import) ───────────────────────────────
142
+ resolveId(id) {
143
+ if (id === VIRTUAL_ID) return RESOLVED_ID;
144
+ },
145
+ load(id) {
146
+ if (id === RESOLVED_ID) return getAllCSS();
147
+ },
148
+ // ── HMR: full reload when new classes are added ───────────────────────────
126
149
  handleHotUpdate({ file }) {
127
150
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
128
151
  try {
129
152
  const added = extractFromCode(readFileSync(file, "utf-8"));
130
- if (added) invalidateCSSModules();
153
+ if (added && server) {
154
+ (server.hot ?? server.ws)?.send({ type: "full-reload" });
155
+ }
131
156
  } catch {
132
157
  }
133
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
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",