@absolutejs/absolute 0.19.0-beta.122 → 0.19.0-beta.124

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.
@@ -240,7 +240,10 @@
240
240
  "Bash(find /home/alexkahn/abs/absolutejs -name *.d.ts -path */node_modules/@types/node/*http2*)",
241
241
  "Bash(fuser -k 3001/tcp)",
242
242
  "WebFetch(domain:groups.google.com)",
243
- "WebFetch(domain:developer.chrome.com)"
243
+ "WebFetch(domain:developer.chrome.com)",
244
+ "Bash(~/alex/bun-combined-patch/build/release/bun h2-benchmark.ts 3000)",
245
+ "Bash(~/alex/bun-combined-patch/build/release/bun h2-benchmark.ts 3000 https)",
246
+ "Bash(fuser -k 3000/tcp)"
244
247
  ]
245
248
  }
246
249
  }
@@ -0,0 +1,81 @@
1
+ # HTTP/2 Dev Server — Status & Roadmap
2
+
3
+ ## Goal
4
+ Serve dev module fetches (/@src/, vendor, etc.) over HTTP/2 multiplexed connections
5
+ to eliminate the HTTP/1.1 6-connection bottleneck on import-heavy pages.
6
+
7
+ ## What's Built & Working
8
+
9
+ ### HTTPS / TLS (shipping now)
10
+ - `dev: { https: true }` config option
11
+ - `src/dev/devCert.ts` — mkcert + self-signed cert generation
12
+ - `src/plugins/networking.ts` — Bun.serve with TLS when HTTPS enabled
13
+ - `src/cli/scripts/dev.ts` — passes `ABSOLUTE_HTTPS=true` to server process
14
+
15
+ ### HTTP/2 Plumbing (ready, waiting on Bun)
16
+ - `src/core/prepare.ts` — exposes `globalThis.__http2Config` when `dev.https` is enabled
17
+ - `src/plugins/hmr.ts` — skips Elysia `.ws('/hmr')` when `__http2Config` is set (h2 mode handles WS differently)
18
+ - `types/globals.d.ts` — `__http2Config` type declaration
19
+ - `types/build.ts` — `dev.https` config type
20
+
21
+ ### RFC 8441 WebSocket over HTTP/2 (tested, not shipping)
22
+ We built and tested WebSocket over HTTP/2 via Extended CONNECT (RFC 8441).
23
+ This runs WebSocket as a multiplexed h2 stream — no separate HTTP/1.1 connection.
24
+ - Browser sends `:method: CONNECT` + `:protocol: websocket` on an h2 stream
25
+ - Server responds `:status: 200`, stream becomes bidirectional WebSocket
26
+ - Minimal WebSocket frame parser/writer handles text messages for HMR
27
+ - Confirmed working with Chrome via Playwright
28
+
29
+ ### Bun Patch: enableConnectProtocol (PR-ready)
30
+ **Repo:** `~/alex/bun-http2-patch` (branch: `feat/http2-enable-connect-protocol`)
31
+
32
+ **The bug:** `session.settings({ enableConnectProtocol: true })` is validated in JS
33
+ and the Zig struct has the field, but `loadSettingsFromJSValue()` in
34
+ `src/bun.js/api/bun/h2_frame_parser.zig` silently ignores it. The setting is never
35
+ sent in the SETTINGS frame, so browsers never try Extended CONNECT.
36
+
37
+ **The fix:** 7 lines in `loadSettingsFromJSValue()` following the exact `enablePush` pattern.
38
+
39
+ **Status:** Built, tested, confirmed working. Ready for PR to oven-sh/bun.
40
+
41
+ ## What's Blocking
42
+
43
+ ### Bun Issue #14672 — HTTP/2 for Bun.serve()
44
+ **This is the critical blocker.** Currently `Bun.serve()` only speaks HTTP/1.1.
45
+ Our JS-level bridge (`node:http2` → `app.fetch()` → `arrayBuffer()` → `stream.end()`)
46
+ works but adds per-request overhead that negates the multiplexing gain at scale:
47
+
48
+ | Metric | HTTP/1.1 (Bun.serve) | HTTP/2 (JS bridge) |
49
+ |---|---|---|
50
+ | Resource fetch sum (250 resources) | 21,626ms | **4,310ms** (5x better) |
51
+ | Page load wall time | **~2,800ms** | ~22,000ms (8x worse) |
52
+
53
+ The h2 multiplexing works (5x faster aggregate fetch), but the JS bridge overhead
54
+ per-request makes overall load 8x slower than Bun.serve's native Zig path.
55
+
56
+ **Track:** https://github.com/oven-sh/bun/issues/14672
57
+
58
+ ### Bun Issue #26721 — allowHTTP1 broken on node:http2
59
+ `allowHTTP1: true` doesn't advertise `http/1.1` in ALPN, so HTTP/1.1 fallback
60
+ for WebSocket upgrade never works. Our RFC 8441 approach bypasses this entirely,
61
+ but it's relevant if someone wants the traditional `ws` upgrade path.
62
+
63
+ **Track:** https://github.com/oven-sh/bun/issues/26721
64
+
65
+ ### Bun Quirk — listen(port, hostname) breaks ALPN
66
+ Passing a hostname to `server.listen(port, hostname)` on a `node:http2` server
67
+ causes ALPN negotiation to fail. `server.listen(port)` works fine.
68
+ Not filed as an issue yet.
69
+
70
+ ## When Bun.serve Gets HTTP/2
71
+
72
+ Once #14672 lands, the path to enable HTTP/2 is:
73
+
74
+ 1. **networking.ts** — pass h2 option to `app.listen()` / `Bun.serve()` config
75
+ 2. **hmr.ts** — the `__http2Config` check already skips `.ws()` in h2 mode
76
+ 3. **WebSocket** — use RFC 8441 Extended CONNECT (requires enableConnectProtocol patch or Bun fixing it natively)
77
+ 4. Remove the `__http2Config` bridge pattern if Bun.serve handles h2 + WS natively
78
+
79
+ ## Combined Patch Build
80
+ **Repo:** `~/alex/bun-combined-patch` (both reactFastRefresh + enableConnectProtocol)
81
+ **Script:** `scripts/use-combined-bun.sh`
package/dist/index.js CHANGED
@@ -205047,188 +205047,6 @@ var init_hmr = __esm(() => {
205047
205047
  init_webSocket();
205048
205048
  });
205049
205049
 
205050
- // src/dev/http2Bridge.ts
205051
- var exports_http2Bridge = {};
205052
- __export(exports_http2Bridge, {
205053
- bridgeHttp2Stream: () => bridgeHttp2Stream
205054
- });
205055
- var WS_OPCODE_TEXT = 1, WS_OPCODE_CLOSE = 8, WS_OPCODE_PING = 9, WS_OPCODE_PONG = 10, parseWsFrame = (buf) => {
205056
- if (buf.length < 2)
205057
- return null;
205058
- const byte0 = buf[0];
205059
- const byte1 = buf[1];
205060
- const opcode = byte0 & 15;
205061
- const masked = (byte1 & 128) !== 0;
205062
- let payloadLen = byte1 & 127;
205063
- let offset = 2;
205064
- if (payloadLen === 126) {
205065
- if (buf.length < 4)
205066
- return null;
205067
- payloadLen = buf.readUInt16BE(2);
205068
- offset = 4;
205069
- } else if (payloadLen === 127) {
205070
- if (buf.length < 10)
205071
- return null;
205072
- payloadLen = Number(buf.readBigUInt64BE(2));
205073
- offset = 10;
205074
- }
205075
- if (masked) {
205076
- if (buf.length < offset + 4 + payloadLen)
205077
- return null;
205078
- const maskKey = buf.subarray(offset, offset + 4);
205079
- offset += 4;
205080
- const payload = Buffer.allocUnsafe(payloadLen);
205081
- for (let i = 0;i < payloadLen; i++) {
205082
- payload[i] = buf[offset + i] ^ maskKey[i & 3];
205083
- }
205084
- return { opcode, payload, totalLen: offset + payloadLen };
205085
- }
205086
- if (buf.length < offset + payloadLen)
205087
- return null;
205088
- return {
205089
- opcode,
205090
- payload: buf.subarray(offset, offset + payloadLen),
205091
- totalLen: offset + payloadLen
205092
- };
205093
- }, writeWsFrame = (opcode, payload) => {
205094
- const len = payload.length;
205095
- let header;
205096
- if (len < 126) {
205097
- header = Buffer.allocUnsafe(2);
205098
- header[0] = 128 | opcode;
205099
- header[1] = len;
205100
- } else if (len < 65536) {
205101
- header = Buffer.allocUnsafe(4);
205102
- header[0] = 128 | opcode;
205103
- header[1] = 126;
205104
- header.writeUInt16BE(len, 2);
205105
- } else {
205106
- header = Buffer.allocUnsafe(10);
205107
- header[0] = 128 | opcode;
205108
- header[1] = 127;
205109
- header.writeBigUInt64BE(BigInt(len), 2);
205110
- }
205111
- return Buffer.concat([header, payload]);
205112
- }, createHttp2WebSocket = (stream) => {
205113
- let state = WS_READY_STATE_OPEN;
205114
- let buffer = Buffer.alloc(0);
205115
- let onMessage = null;
205116
- let onClose = null;
205117
- stream.on("data", (chunk) => {
205118
- buffer = Buffer.concat([buffer, chunk]);
205119
- while (buffer.length > 0) {
205120
- const frame = parseWsFrame(buffer);
205121
- if (!frame)
205122
- break;
205123
- buffer = buffer.subarray(frame.totalLen);
205124
- if (frame.opcode === WS_OPCODE_TEXT && onMessage) {
205125
- onMessage(frame.payload.toString("utf-8"));
205126
- } else if (frame.opcode === WS_OPCODE_PING) {
205127
- if (!stream.destroyed) {
205128
- stream.write(writeWsFrame(WS_OPCODE_PONG, frame.payload));
205129
- }
205130
- } else if (frame.opcode === WS_OPCODE_CLOSE) {
205131
- if (!stream.destroyed) {
205132
- stream.write(writeWsFrame(WS_OPCODE_CLOSE, Buffer.alloc(0)));
205133
- stream.end();
205134
- }
205135
- state = 3;
205136
- if (onClose)
205137
- onClose();
205138
- }
205139
- }
205140
- });
205141
- stream.on("close", () => {
205142
- if (state === WS_READY_STATE_OPEN) {
205143
- state = 3;
205144
- if (onClose)
205145
- onClose();
205146
- }
205147
- });
205148
- stream.on("error", () => {
205149
- state = 3;
205150
- });
205151
- const ws = {
205152
- get readyState() {
205153
- return state;
205154
- },
205155
- send(data) {
205156
- if (state !== WS_READY_STATE_OPEN || stream.destroyed)
205157
- return;
205158
- stream.write(writeWsFrame(WS_OPCODE_TEXT, Buffer.from(data)));
205159
- },
205160
- close() {
205161
- if (state !== WS_READY_STATE_OPEN || stream.destroyed)
205162
- return;
205163
- stream.write(writeWsFrame(WS_OPCODE_CLOSE, Buffer.alloc(0)));
205164
- stream.end();
205165
- state = 2;
205166
- },
205167
- onMessage: null,
205168
- onClose: null
205169
- };
205170
- onMessage = (data) => ws.onMessage?.(data);
205171
- onClose = () => ws.onClose?.();
205172
- return ws;
205173
- }, bridgeHttp2Stream = async (stream, headers, fetchHandler, hmrState2, manifest) => {
205174
- const method = headers[":method"] ?? "GET";
205175
- const path = headers[":path"] ?? "/";
205176
- if (method === "CONNECT" && headers[":protocol"] === "websocket" && hmrState2 && manifest) {
205177
- stream.respond({ ":status": 200 });
205178
- const ws = createHttp2WebSocket(stream);
205179
- ws.onMessage = (data) => handleHMRMessage(hmrState2, ws, data);
205180
- ws.onClose = () => handleClientDisconnect(hmrState2, ws);
205181
- handleClientConnect(hmrState2, ws, manifest);
205182
- return;
205183
- }
205184
- const authority = headers[":authority"] ?? "localhost";
205185
- const scheme = headers[":scheme"] ?? "https";
205186
- const url = `${scheme}://${authority}${path}`;
205187
- const requestHeaders = new Headers;
205188
- for (const [key, value] of Object.entries(headers)) {
205189
- if (key.startsWith(":") || value === undefined)
205190
- continue;
205191
- const headerValue = Array.isArray(value) ? value.join(", ") : value;
205192
- requestHeaders.set(key, headerValue);
205193
- }
205194
- const hasBody = method !== "GET" && method !== "HEAD";
205195
- const bodyBlob = hasBody ? await new Promise((resolve24) => {
205196
- const chunks = [];
205197
- stream.on("data", (chunk) => chunks.push(chunk));
205198
- stream.on("end", () => {
205199
- resolve24(new Blob([Buffer.concat(chunks)]));
205200
- });
205201
- }) : null;
205202
- const request = new Request(url, {
205203
- body: bodyBlob,
205204
- headers: requestHeaders,
205205
- method
205206
- });
205207
- try {
205208
- const response = await fetchHandler(request);
205209
- const responseHeaders = {
205210
- ":status": response.status
205211
- };
205212
- response.headers.forEach((value, key) => {
205213
- responseHeaders[key] = value;
205214
- });
205215
- if (!response.body) {
205216
- stream.respond(responseHeaders);
205217
- stream.end();
205218
- return;
205219
- }
205220
- const arrayBuffer = await response.arrayBuffer();
205221
- stream.respond(responseHeaders);
205222
- stream.end(Buffer.from(arrayBuffer));
205223
- } catch {
205224
- stream.respond({ ":status": 500, "content-type": "text/plain" });
205225
- stream.end("Internal Server Error");
205226
- }
205227
- };
205228
- var init_http2Bridge = __esm(() => {
205229
- init_webSocket();
205230
- });
205231
-
205232
205050
  // src/dev/devCert.ts
205233
205051
  var exports_devCert = {};
205234
205052
  __export(exports_devCert, {
@@ -205580,10 +205398,12 @@ var prepare = async (configOrPath) => {
205580
205398
  warmCache2(`${SRC_URL_PREFIX2}${rel}`);
205581
205399
  }
205582
205400
  }
205583
- globalThis.__http2Config = {
205584
- hmrState: result.hmrState,
205585
- manifest: result.manifest
205586
- };
205401
+ if (config.dev?.https) {
205402
+ globalThis.__http2Config = {
205403
+ hmrState: result.hmrState,
205404
+ manifest: result.manifest
205405
+ };
205406
+ }
205587
205407
  const hmrPlugin = hmr2(result.hmrState, result.manifest, moduleHandler);
205588
205408
  const devIndexDir = resolve23(buildDir, "_src_indexes");
205589
205409
  for (const key of Object.keys(result.manifest)) {
@@ -205610,8 +205430,6 @@ var pageRouterPlugin = () => {
205610
205430
  };
205611
205431
  // src/plugins/networking.ts
205612
205432
  init_constants();
205613
- import { readFileSync as readFileSync12 } from "fs";
205614
- import { join as join18 } from "path";
205615
205433
  import { argv } from "process";
205616
205434
  var {env: env3 } = globalThis.Bun;
205617
205435
 
@@ -205644,65 +205462,45 @@ if (hostFlag) {
205644
205462
  localIP = getLocalIPAddress();
205645
205463
  host = "0.0.0.0";
205646
205464
  }
205647
- var isHttpsDev = env3.NODE_ENV === "development" && env3.ABSOLUTE_HTTPS === "true";
205648
- var protocol = isHttpsDev ? "https" : "http";
205649
- var showBanner = () => {
205465
+ var tls = (() => {
205466
+ if (env3.NODE_ENV !== "development")
205467
+ return;
205468
+ if (env3.ABSOLUTE_HTTPS !== "true")
205469
+ return;
205470
+ try {
205471
+ const { loadDevCert: loadDevCert2 } = (init_devCert(), __toCommonJS(exports_devCert));
205472
+ return loadDevCert2();
205473
+ } catch {
205474
+ return;
205475
+ }
205476
+ })();
205477
+ var protocol = tls ? "https" : "http";
205478
+ var networking = (app) => app.listen({
205479
+ hostname: host,
205480
+ port,
205481
+ ...tls ? {
205482
+ tls: {
205483
+ cert: tls.cert,
205484
+ key: tls.key
205485
+ }
205486
+ } : {}
205487
+ }, () => {
205650
205488
  const isHotReload = Boolean(globalThis.__hmrServerStartup);
205651
205489
  globalThis.__hmrServerStartup = true;
205652
- if (isHotReload)
205490
+ if (isHotReload) {
205653
205491
  return;
205492
+ }
205493
+ const buildDuration = globalThis.__hmrBuildDuration ?? Number(env3.ABSOLUTE_BUILD_DURATION || 0);
205494
+ const version = globalThis.__absoluteVersion || env3.ABSOLUTE_VERSION || "";
205654
205495
  startupBanner({
205655
- duration: globalThis.__hmrBuildDuration ?? Number(env3.ABSOLUTE_BUILD_DURATION || 0),
205496
+ duration: buildDuration,
205656
205497
  host,
205657
205498
  networkUrl: hostFlag ? `${protocol}://${localIP}:${port}/` : undefined,
205658
205499
  port,
205659
205500
  protocol,
205660
- version: globalThis.__absoluteVersion || env3.ABSOLUTE_VERSION || ""
205661
- });
205662
- };
205663
- var networking = (app) => {
205664
- if (isHttpsDev) {
205665
- const certDir = join18(process.cwd(), ".absolutejs");
205666
- const cert = readFileSync12(join18(certDir, "cert.pem"), "utf-8");
205667
- const key = readFileSync12(join18(certDir, "key.pem"), "utf-8");
205668
- app.compile();
205669
- const http2 = __require("http2");
205670
- const server2 = http2.createSecureServer({
205671
- cert,
205672
- key,
205673
- settings: { enableConnectProtocol: true }
205674
- });
205675
- server2.on("session", (session) => {
205676
- session.settings({ enableConnectProtocol: true });
205677
- });
205678
- const { bridgeHttp2Stream: bridgeHttp2Stream2 } = (init_http2Bridge(), __toCommonJS(exports_http2Bridge));
205679
- const http2Config = globalThis.__http2Config;
205680
- server2.on("stream", (stream, headers) => {
205681
- bridgeHttp2Stream2(stream, headers, app.fetch.bind(app), http2Config?.hmrState, http2Config?.manifest);
205682
- });
205683
- server2.listen(Number(port), () => {
205684
- showBanner();
205685
- });
205686
- return app;
205687
- }
205688
- const tls = (() => {
205689
- if (!isHttpsDev)
205690
- return;
205691
- try {
205692
- const { loadDevCert: loadDevCert2 } = (init_devCert(), __toCommonJS(exports_devCert));
205693
- return loadDevCert2();
205694
- } catch {
205695
- return;
205696
- }
205697
- })();
205698
- return app.listen({
205699
- hostname: host,
205700
- port,
205701
- ...tls ? { tls: { cert: tls.cert, key: tls.key } } : {}
205702
- }, () => {
205703
- showBanner();
205501
+ version
205704
205502
  });
205705
- };
205503
+ });
205706
205504
  // src/utils/defineConfig.ts
205707
205505
  var defineConfig = (config) => config;
205708
205506
  // src/utils/generateHeadElement.ts
@@ -205802,5 +205600,5 @@ export {
205802
205600
  ANGULAR_INIT_TIMEOUT_MS
205803
205601
  };
205804
205602
 
205805
- //# debugId=781BD03FD0AB276C64756E2164756E21
205603
+ //# debugId=E99B445C59288BB764756E2164756E21
205806
205604
  //# sourceMappingURL=index.js.map