@kbach/react 0.5.20 → 0.5.22

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.
@@ -5,7 +5,7 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.mjs';
5
5
  * @kbach/react/vite
6
6
  *
7
7
  * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
- * Zero setup: just add kbach() to your plugins array. CSS is auto-injected.
8
+ * Zero setup: just add kbach() to your plugins array.
9
9
  * Only generates CSS for classes you actually use (like Tailwind).
10
10
  */
11
11
 
@@ -5,7 +5,7 @@ import { F as FrameworkConfig } from './types-CV5vXnSH.js';
5
5
  * @kbach/react/vite
6
6
  *
7
7
  * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
- * Zero setup: just add kbach() to your plugins array. CSS is auto-injected.
8
+ * Zero setup: just add kbach() to your plugins array.
9
9
  * Only generates CSS for classes you actually use (like Tailwind).
10
10
  */
11
11
 
@@ -3397,7 +3397,6 @@ function kbach(userConfig) {
3397
3397
  const tokenCSS = /* @__PURE__ */ new Map();
3398
3398
  let root = process.cwd();
3399
3399
  let server = null;
3400
- let isDev = false;
3401
3400
  function processClassString(classStr) {
3402
3401
  let added = false;
3403
3402
  for (const token of classStr.split(/\s+/).filter(Boolean)) {
@@ -3423,42 +3422,82 @@ function kbach(userConfig) {
3423
3422
  }
3424
3423
  return parts.join("\n");
3425
3424
  }
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
+ }
3426
3430
  return {
3427
3431
  name: "kbach",
3428
3432
  enforce: "pre",
3429
- config(_c, env) {
3430
- isDev = env.command === "serve";
3431
- },
3432
3433
  configResolved(resolved) {
3433
3434
  root = resolved.root;
3434
3435
  },
3435
- configureServer(s) {
3436
- server = s;
3437
- },
3438
3436
  buildStart() {
3439
3437
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
3440
3438
  scanDir((0, import_path.join)(root, dir), (code) => extractFromCode(code));
3441
3439
  }
3442
3440
  },
3443
- // ── Primary injection: inject a <style> tag into every HTML response ──────
3444
- // React Router v8's dev middleware calls server.transformIndexHtml() on
3445
- // every SSR HTML response, so this hook fires on every page load.
3446
- transformIndexHtml: {
3447
- order: "post",
3448
- handler() {
3449
- const css = getAllCSS();
3450
- if (!css) return [];
3451
- return [{
3452
- tag: "style",
3453
- attrs: { "data-kbach": "" },
3454
- children: css,
3455
- injectTo: "head"
3456
- }];
3457
- }
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
+ res.write = function(chunk, enc, cb) {
3457
+ if (done) return origWrite(chunk, enc, cb);
3458
+ if (chunk != null) {
3459
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk ?? "")));
3460
+ }
3461
+ if (typeof enc === "function") enc();
3462
+ else if (typeof cb === "function") cb();
3463
+ return true;
3464
+ };
3465
+ res.end = function(chunk, enc, cb) {
3466
+ done = true;
3467
+ if (typeof chunk === "function") {
3468
+ cb = chunk;
3469
+ chunk = null;
3470
+ enc = null;
3471
+ } else if (typeof enc === "function") {
3472
+ cb = enc;
3473
+ enc = null;
3474
+ }
3475
+ if (chunk != null) {
3476
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk ?? "")));
3477
+ }
3478
+ if (chunks.length > 0) {
3479
+ const html = Buffer.concat(chunks).toString("utf-8");
3480
+ if (html.includes("</head>")) {
3481
+ const injected = injectIntoHtml(html);
3482
+ const buf = Buffer.from(injected, "utf-8");
3483
+ try {
3484
+ res.removeHeader("transfer-encoding");
3485
+ } catch {
3486
+ }
3487
+ try {
3488
+ res.setHeader("content-length", buf.length);
3489
+ } catch {
3490
+ }
3491
+ return origEnd(buf, cb);
3492
+ }
3493
+ return origEnd(Buffer.concat(chunks), enc, cb);
3494
+ }
3495
+ return origEnd(void 0, enc, cb);
3496
+ };
3497
+ next();
3498
+ });
3458
3499
  },
3459
- // ── Secondary: also inject into CSS files (for production builds) ─────────
3460
- // In production, Vite extracts CSS from JS into separate .css files.
3461
- // Appending to the user's CSS ensures it ends up in the final CSS bundle.
3500
+ // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
3462
3501
  transform(code, id) {
3463
3502
  const cleanId = id.split("?")[0];
3464
3503
  if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
@@ -3483,7 +3522,7 @@ function kbach(userConfig) {
3483
3522
  try {
3484
3523
  const added = extractFromCode((0, import_fs.readFileSync)(file, "utf-8"));
3485
3524
  if (added && server) {
3486
- server.hot?.send({ type: "full-reload" }) ?? server.ws?.send({ type: "full-reload" });
3525
+ (server.hot ?? server.ws)?.send({ type: "full-reload" });
3487
3526
  }
3488
3527
  } catch {
3489
3528
  }
@@ -55,7 +55,6 @@ function kbach(userConfig) {
55
55
  const tokenCSS = /* @__PURE__ */ new Map();
56
56
  let root = process.cwd();
57
57
  let server = null;
58
- let isDev = false;
59
58
  function processClassString(classStr) {
60
59
  let added = false;
61
60
  for (const token of classStr.split(/\s+/).filter(Boolean)) {
@@ -81,42 +80,82 @@ function kbach(userConfig) {
81
80
  }
82
81
  return parts.join("\n");
83
82
  }
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
+ }
84
88
  return {
85
89
  name: "kbach",
86
90
  enforce: "pre",
87
- config(_c, env) {
88
- isDev = env.command === "serve";
89
- },
90
91
  configResolved(resolved) {
91
92
  root = resolved.root;
92
93
  },
93
- configureServer(s) {
94
- server = s;
95
- },
96
94
  buildStart() {
97
95
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
98
96
  scanDir(join(root, dir), (code) => extractFromCode(code));
99
97
  }
100
98
  },
101
- // ── Primary injection: inject a <style> tag into every HTML response ──────
102
- // React Router v8's dev middleware calls server.transformIndexHtml() on
103
- // every SSR HTML response, so this hook fires on every page load.
104
- transformIndexHtml: {
105
- order: "post",
106
- handler() {
107
- const css = getAllCSS();
108
- if (!css) return [];
109
- return [{
110
- tag: "style",
111
- attrs: { "data-kbach": "" },
112
- children: css,
113
- injectTo: "head"
114
- }];
115
- }
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
+ res.write = function(chunk, enc, cb) {
115
+ if (done) return origWrite(chunk, enc, cb);
116
+ if (chunk != null) {
117
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk ?? "")));
118
+ }
119
+ if (typeof enc === "function") enc();
120
+ else if (typeof cb === "function") cb();
121
+ return true;
122
+ };
123
+ res.end = function(chunk, enc, cb) {
124
+ done = true;
125
+ if (typeof chunk === "function") {
126
+ cb = chunk;
127
+ chunk = null;
128
+ enc = null;
129
+ } else if (typeof enc === "function") {
130
+ cb = enc;
131
+ enc = null;
132
+ }
133
+ if (chunk != null) {
134
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk ?? "")));
135
+ }
136
+ if (chunks.length > 0) {
137
+ const html = Buffer.concat(chunks).toString("utf-8");
138
+ if (html.includes("</head>")) {
139
+ const injected = injectIntoHtml(html);
140
+ const buf = Buffer.from(injected, "utf-8");
141
+ try {
142
+ res.removeHeader("transfer-encoding");
143
+ } catch {
144
+ }
145
+ try {
146
+ res.setHeader("content-length", buf.length);
147
+ } catch {
148
+ }
149
+ return origEnd(buf, cb);
150
+ }
151
+ return origEnd(Buffer.concat(chunks), enc, cb);
152
+ }
153
+ return origEnd(void 0, enc, cb);
154
+ };
155
+ next();
156
+ });
116
157
  },
117
- // ── Secondary: also inject into CSS files (for production builds) ─────────
118
- // In production, Vite extracts CSS from JS into separate .css files.
119
- // Appending to the user's CSS ensures it ends up in the final CSS bundle.
158
+ // ── PROD: append to CSS files so they land in the extracted CSS bundle ────
120
159
  transform(code, id) {
121
160
  const cleanId = id.split("?")[0];
122
161
  if (cleanId.endsWith(".css") && !id.includes("node_modules") && !id.includes("\0")) {
@@ -141,7 +180,7 @@ function kbach(userConfig) {
141
180
  try {
142
181
  const added = extractFromCode(readFileSync(file, "utf-8"));
143
182
  if (added && server) {
144
- server.hot?.send({ type: "full-reload" }) ?? server.ws?.send({ type: "full-reload" });
183
+ (server.hot ?? server.ws)?.send({ type: "full-reload" });
145
184
  }
146
185
  } catch {
147
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
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",