@kbach/react 0.5.21 → 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.
@@ -3438,32 +3438,61 @@ function kbach(userConfig) {
3438
3438
  scanDir((0, import_path.join)(root, dir), (code) => extractFromCode(code));
3439
3439
  }
3440
3440
  },
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.
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.
3444
3445
  configureServer(s) {
3445
3446
  server = s;
3446
- s.middlewares.use((_req, res, next) => {
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);
3447
3453
  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
- }
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 {
3462
3490
  }
3491
+ return origEnd(buf, cb);
3463
3492
  }
3464
- } catch {
3493
+ return origEnd(Buffer.concat(chunks), enc, cb);
3465
3494
  }
3466
- return origEnd(chunk, encoding, cb);
3495
+ return origEnd(void 0, enc, cb);
3467
3496
  };
3468
3497
  next();
3469
3498
  });
@@ -96,32 +96,61 @@ function kbach(userConfig) {
96
96
  scanDir(join(root, dir), (code) => extractFromCode(code));
97
97
  }
98
98
  },
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.
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.
102
103
  configureServer(s) {
103
104
  server = s;
104
- s.middlewares.use((_req, res, next) => {
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);
105
111
  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
- }
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 {
120
148
  }
149
+ return origEnd(buf, cb);
121
150
  }
122
- } catch {
151
+ return origEnd(Buffer.concat(chunks), enc, cb);
123
152
  }
124
- return origEnd(chunk, encoding, cb);
153
+ return origEnd(void 0, enc, cb);
125
154
  };
126
155
  next();
127
156
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.21",
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",