hadars 0.3.0 → 0.3.1
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.
- package/README.md +14 -14
- package/dist/{chunk-H72BZXOA.js → chunk-2KJRDPCN.js} +12 -2
- package/dist/cli.js +13 -22
- package/dist/cloudflare.cjs +12 -2
- package/dist/cloudflare.js +1 -1
- package/dist/lambda.cjs +12 -2
- package/dist/lambda.js +1 -1
- package/package.json +1 -1
- package/src/utils/serve.ts +1 -28
- package/src/utils/ssrHandler.ts +22 -2
package/README.md
CHANGED
|
@@ -19,30 +19,30 @@ Bring your own router (or none), keep your components as plain React, and get SS
|
|
|
19
19
|
## Benchmarks
|
|
20
20
|
|
|
21
21
|
<!-- BENCHMARK_START -->
|
|
22
|
-
> Last run: 2026-03-
|
|
23
|
-
> hadars is **8.
|
|
22
|
+
> Last run: 2026-03-25 · 120s · 100 connections · Bun runtime
|
|
23
|
+
> hadars is **8.9x faster** in requests/sec
|
|
24
24
|
|
|
25
25
|
**Throughput** (autocannon, 120s)
|
|
26
26
|
|
|
27
27
|
| Metric | hadars | Next.js |
|
|
28
28
|
|---|---:|---:|
|
|
29
|
-
| Requests/sec | **
|
|
30
|
-
| Latency median | **
|
|
31
|
-
| Latency p99 | **
|
|
32
|
-
| Throughput | **
|
|
33
|
-
| Peak RSS |
|
|
34
|
-
| Avg RSS |
|
|
35
|
-
| Build time | 0.
|
|
29
|
+
| Requests/sec | **151** | 17 |
|
|
30
|
+
| Latency median | **642 ms** | 2747 ms |
|
|
31
|
+
| Latency p99 | **959 ms** | 4019 ms |
|
|
32
|
+
| Throughput | **43.05** MB/s | 9.5 MB/s |
|
|
33
|
+
| Peak RSS | 950.3 MB | **478.5 MB** |
|
|
34
|
+
| Avg RSS | 763.3 MB | **426.4 MB** |
|
|
35
|
+
| Build time | 0.7 s | 6.0 s |
|
|
36
36
|
|
|
37
37
|
**Page load** (Playwright · Chromium headless · median)
|
|
38
38
|
|
|
39
39
|
| Metric | hadars | Next.js |
|
|
40
40
|
|---|---:|---:|
|
|
41
|
-
| TTFB | **
|
|
42
|
-
| FCP | **96 ms** |
|
|
43
|
-
| DOMContentLoaded | **39 ms** |
|
|
44
|
-
| Load | **122 ms** |
|
|
45
|
-
| Peak RSS |
|
|
41
|
+
| TTFB | **19 ms** | 42 ms |
|
|
42
|
+
| FCP | **96 ms** | 136 ms |
|
|
43
|
+
| DOMContentLoaded | **39 ms** | 127 ms |
|
|
44
|
+
| Load | **122 ms** | 173 ms |
|
|
45
|
+
| Peak RSS | 476.8 MB | **289.5 MB** |
|
|
46
46
|
<!-- BENCHMARK_END -->
|
|
47
47
|
|
|
48
48
|
## Quick start
|
|
@@ -264,8 +264,18 @@ async function transformStream(data, stream) {
|
|
|
264
264
|
}
|
|
265
265
|
return out;
|
|
266
266
|
}
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
async function zlibGzip(d) {
|
|
268
|
+
const zlib = await import("node:zlib");
|
|
269
|
+
const { promisify } = await import("node:util");
|
|
270
|
+
return promisify(zlib.gzip)(d);
|
|
271
|
+
}
|
|
272
|
+
async function zlibGunzip(d) {
|
|
273
|
+
const zlib = await import("node:zlib");
|
|
274
|
+
const { promisify } = await import("node:util");
|
|
275
|
+
return promisify(zlib.gunzip)(d);
|
|
276
|
+
}
|
|
277
|
+
var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
|
|
278
|
+
var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
|
|
269
279
|
async function buildCacheEntry(res, ttl) {
|
|
270
280
|
const buf = await res.arrayBuffer();
|
|
271
281
|
const body = await gzipCompress(new Uint8Array(buf));
|
package/dist/cli.js
CHANGED
|
@@ -1626,27 +1626,8 @@ function withRequestLogging(handler) {
|
|
|
1626
1626
|
return res;
|
|
1627
1627
|
};
|
|
1628
1628
|
}
|
|
1629
|
-
var COMPRESSIBLE_RE = /^text\/|\/json|\/javascript|\/xml|\/wasm/;
|
|
1630
|
-
function withCompression(handler) {
|
|
1631
|
-
return async (req, ctx) => {
|
|
1632
|
-
const res = await handler(req, ctx);
|
|
1633
|
-
if (!res || !res.body) return res;
|
|
1634
|
-
const accept = req.headers.get("Accept-Encoding") ?? "";
|
|
1635
|
-
if (!accept.includes("gzip")) return res;
|
|
1636
|
-
if (res.headers.has("content-encoding")) return res;
|
|
1637
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
1638
|
-
if (!COMPRESSIBLE_RE.test(ct)) return res;
|
|
1639
|
-
const compressed = res.body.pipeThrough(
|
|
1640
|
-
new globalThis.CompressionStream("gzip")
|
|
1641
|
-
);
|
|
1642
|
-
const headers = new Headers(res.headers);
|
|
1643
|
-
headers.set("content-encoding", "gzip");
|
|
1644
|
-
headers.delete("content-length");
|
|
1645
|
-
return new Response(compressed, { status: res.status, headers });
|
|
1646
|
-
};
|
|
1647
|
-
}
|
|
1648
1629
|
async function serve(port, fetchHandler, websocket) {
|
|
1649
|
-
fetchHandler =
|
|
1630
|
+
fetchHandler = withRequestLogging(fetchHandler);
|
|
1650
1631
|
if (isBun) {
|
|
1651
1632
|
globalThis.Bun.serve({
|
|
1652
1633
|
port,
|
|
@@ -1839,8 +1820,18 @@ async function transformStream(data, stream) {
|
|
|
1839
1820
|
}
|
|
1840
1821
|
return out;
|
|
1841
1822
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1823
|
+
async function zlibGzip(d) {
|
|
1824
|
+
const zlib = await import("node:zlib");
|
|
1825
|
+
const { promisify } = await import("node:util");
|
|
1826
|
+
return promisify(zlib.gzip)(d);
|
|
1827
|
+
}
|
|
1828
|
+
async function zlibGunzip(d) {
|
|
1829
|
+
const zlib = await import("node:zlib");
|
|
1830
|
+
const { promisify } = await import("node:util");
|
|
1831
|
+
return promisify(zlib.gunzip)(d);
|
|
1832
|
+
}
|
|
1833
|
+
var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
|
|
1834
|
+
var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
|
|
1844
1835
|
async function buildCacheEntry(res, ttl) {
|
|
1845
1836
|
const buf = await res.arrayBuffer();
|
|
1846
1837
|
const body = await gzipCompress(new Uint8Array(buf));
|
package/dist/cloudflare.cjs
CHANGED
|
@@ -1279,8 +1279,18 @@ async function transformStream(data, stream) {
|
|
|
1279
1279
|
}
|
|
1280
1280
|
return out;
|
|
1281
1281
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1282
|
+
async function zlibGzip(d) {
|
|
1283
|
+
const zlib = await import("zlib");
|
|
1284
|
+
const { promisify } = await import("util");
|
|
1285
|
+
return promisify(zlib.gzip)(d);
|
|
1286
|
+
}
|
|
1287
|
+
async function zlibGunzip(d) {
|
|
1288
|
+
const zlib = await import("zlib");
|
|
1289
|
+
const { promisify } = await import("util");
|
|
1290
|
+
return promisify(zlib.gunzip)(d);
|
|
1291
|
+
}
|
|
1292
|
+
var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
|
|
1293
|
+
var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
|
|
1284
1294
|
async function buildCacheEntry(res, ttl) {
|
|
1285
1295
|
const buf = await res.arrayBuffer();
|
|
1286
1296
|
const body = await gzipCompress(new Uint8Array(buf));
|
package/dist/cloudflare.js
CHANGED
package/dist/lambda.cjs
CHANGED
|
@@ -1319,8 +1319,18 @@ async function transformStream(data, stream) {
|
|
|
1319
1319
|
}
|
|
1320
1320
|
return out;
|
|
1321
1321
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1322
|
+
async function zlibGzip(d) {
|
|
1323
|
+
const zlib = await import("zlib");
|
|
1324
|
+
const { promisify } = await import("util");
|
|
1325
|
+
return promisify(zlib.gzip)(d);
|
|
1326
|
+
}
|
|
1327
|
+
async function zlibGunzip(d) {
|
|
1328
|
+
const zlib = await import("zlib");
|
|
1329
|
+
const { promisify } = await import("util");
|
|
1330
|
+
return promisify(zlib.gunzip)(d);
|
|
1331
|
+
}
|
|
1332
|
+
var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
|
|
1333
|
+
var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
|
|
1324
1334
|
async function buildCacheEntry(res, ttl) {
|
|
1325
1335
|
const buf = await res.arrayBuffer();
|
|
1326
1336
|
const body = await gzipCompress(new Uint8Array(buf));
|
package/dist/lambda.js
CHANGED
package/package.json
CHANGED
package/src/utils/serve.ts
CHANGED
|
@@ -58,33 +58,6 @@ function withRequestLogging(handler: FetchHandler): FetchHandler {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const COMPRESSIBLE_RE = /^text\/|\/json|\/javascript|\/xml|\/wasm/;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Wraps a handler to apply on-the-fly gzip compression via CompressionStream.
|
|
65
|
-
* Unlike a buffering approach, this pipes the response ReadableStream through
|
|
66
|
-
* a CompressionStream so bytes are compressed and flushed as they arrive —
|
|
67
|
-
* streaming responses remain streaming.
|
|
68
|
-
*/
|
|
69
|
-
function withCompression(handler: FetchHandler): FetchHandler {
|
|
70
|
-
return async (req, ctx) => {
|
|
71
|
-
const res = await handler(req, ctx);
|
|
72
|
-
if (!res || !res.body) return res;
|
|
73
|
-
const accept = req.headers.get('Accept-Encoding') ?? '';
|
|
74
|
-
if (!accept.includes('gzip')) return res;
|
|
75
|
-
// Skip if already encoded (e.g. pre-compressed cache entry).
|
|
76
|
-
if (res.headers.has('content-encoding')) return res;
|
|
77
|
-
const ct = res.headers.get('content-type') ?? '';
|
|
78
|
-
if (!COMPRESSIBLE_RE.test(ct)) return res;
|
|
79
|
-
const compressed = res.body.pipeThrough(
|
|
80
|
-
new (globalThis as any).CompressionStream('gzip'),
|
|
81
|
-
);
|
|
82
|
-
const headers = new Headers(res.headers);
|
|
83
|
-
headers.set('content-encoding', 'gzip');
|
|
84
|
-
headers.delete('content-length'); // length is unknown after compression
|
|
85
|
-
return new Response(compressed, { status: res.status, headers });
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
61
|
|
|
89
62
|
export async function serve(
|
|
90
63
|
port: number,
|
|
@@ -92,7 +65,7 @@ export async function serve(
|
|
|
92
65
|
/** Bun WebSocketHandler — ignored on Deno and Node.js. */
|
|
93
66
|
websocket?: unknown,
|
|
94
67
|
): Promise<void> {
|
|
95
|
-
fetchHandler =
|
|
68
|
+
fetchHandler = withRequestLogging(fetchHandler);
|
|
96
69
|
|
|
97
70
|
// ── Bun ────────────────────────────────────────────────────────────────
|
|
98
71
|
if (isBun) {
|
package/src/utils/ssrHandler.ts
CHANGED
|
@@ -142,8 +142,28 @@ async function transformStream(
|
|
|
142
142
|
return out;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
// Use the Web Streams CompressionStream when available (Bun, Deno, Node ≥ 18).
|
|
146
|
+
// Fall back to node:zlib on older Node versions.
|
|
147
|
+
async function zlibGzip(d: Uint8Array): Promise<Uint8Array> {
|
|
148
|
+
const zlib = await import('node:zlib');
|
|
149
|
+
const { promisify } = await import('node:util');
|
|
150
|
+
return promisify(zlib.gzip)(d) as Promise<Uint8Array>;
|
|
151
|
+
}
|
|
152
|
+
async function zlibGunzip(d: Uint8Array): Promise<Uint8Array> {
|
|
153
|
+
const zlib = await import('node:zlib');
|
|
154
|
+
const { promisify } = await import('node:util');
|
|
155
|
+
return promisify(zlib.gunzip)(d) as Promise<Uint8Array>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const gzipCompress = (d: Uint8Array): Promise<Uint8Array> =>
|
|
159
|
+
(globalThis as any).CompressionStream
|
|
160
|
+
? transformStream(d, new (globalThis as any).CompressionStream('gzip'))
|
|
161
|
+
: zlibGzip(d);
|
|
162
|
+
|
|
163
|
+
export const gzipDecompress = (d: Uint8Array): Promise<Uint8Array> =>
|
|
164
|
+
(globalThis as any).DecompressionStream
|
|
165
|
+
? transformStream(d, new (globalThis as any).DecompressionStream('gzip'))
|
|
166
|
+
: zlibGunzip(d);
|
|
147
167
|
|
|
148
168
|
export async function buildCacheEntry(res: Response, ttl: number | undefined): Promise<CacheEntry> {
|
|
149
169
|
const buf = await res.arrayBuffer();
|