hadars 0.1.40 → 0.2.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 +85 -70
- package/cli-lib.ts +89 -12
- package/dist/chunk-HWOLYLPF.js +332 -0
- package/dist/{chunk-2ENP7IAW.js → chunk-LY5MTHFV.js} +360 -203
- package/dist/cli.js +506 -274
- package/dist/cloudflare.cjs +1394 -0
- package/dist/cloudflare.d.cts +64 -0
- package/dist/cloudflare.d.ts +64 -0
- package/dist/cloudflare.js +68 -0
- package/dist/{hadars-Bh-V5YXg.d.cts → hadars-DEBSYAQl.d.cts} +1 -36
- package/dist/{hadars-Bh-V5YXg.d.ts → hadars-DEBSYAQl.d.ts} +1 -36
- package/dist/index.cjs +129 -156
- package/dist/index.d.cts +5 -11
- package/dist/index.d.ts +5 -11
- package/dist/index.js +129 -155
- package/dist/lambda.cjs +391 -229
- package/dist/lambda.d.cts +1 -2
- package/dist/lambda.d.ts +1 -2
- package/dist/lambda.js +18 -307
- package/dist/slim-react/index.cjs +361 -203
- package/dist/slim-react/index.d.cts +24 -8
- package/dist/slim-react/index.d.ts +24 -8
- package/dist/slim-react/index.js +3 -1
- package/dist/ssr-render-worker.js +352 -221
- package/dist/utils/Head.tsx +132 -187
- package/package.json +7 -2
- package/src/build.ts +7 -6
- package/src/cloudflare.ts +139 -0
- package/src/index.tsx +0 -3
- package/src/lambda.ts +6 -2
- package/src/slim-react/context.ts +2 -1
- package/src/slim-react/index.ts +21 -18
- package/src/slim-react/render.ts +379 -240
- package/src/slim-react/renderContext.ts +105 -45
- package/src/ssr-render-worker.ts +14 -44
- package/src/types/hadars.ts +0 -1
- package/src/utils/Head.tsx +132 -187
- package/src/utils/cookies.ts +1 -1
- package/src/utils/response.tsx +68 -33
- package/src/utils/serve.ts +29 -27
- package/src/utils/ssrHandler.ts +54 -25
- package/src/utils/staticFile.ts +2 -7
package/src/utils/ssrHandler.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseRequest } from './request';
|
|
2
|
-
import
|
|
2
|
+
import { buildHeadHtml } from './response';
|
|
3
|
+
import type { AppHead, HadarsOptions } from '../types/hadars';
|
|
3
4
|
|
|
4
5
|
export const HEAD_MARKER = '<meta name="HADARS_HEAD">';
|
|
5
6
|
export const BODY_MARKER = '<meta name="HADARS_BODY">';
|
|
@@ -8,28 +9,50 @@ const encoder = new TextEncoder();
|
|
|
8
9
|
|
|
9
10
|
// ── HTML response assembly ────────────────────────────────────────────────────
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
clientProps: Record<string, unknown>,
|
|
14
|
-
headHtml: string,
|
|
12
|
+
export function buildSsrResponse(
|
|
13
|
+
head: AppHead,
|
|
15
14
|
status: number,
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
getAppBody: () => Promise<string>,
|
|
16
|
+
finalize: () => Promise<{ clientProps: Record<string, unknown> }>,
|
|
17
|
+
getPrecontentHtml: (headHtml: string) => [string, string] | Promise<[string, string]>,
|
|
18
|
+
): Response {
|
|
19
|
+
const headHtml = buildHeadHtml(head);
|
|
20
|
+
const precontentResult = getPrecontentHtml(headHtml);
|
|
21
|
+
|
|
18
22
|
const responseStream = new ReadableStream({
|
|
19
23
|
async start(controller) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
try {
|
|
25
|
+
// Resolve the template — sync on the hot path (every request after the first).
|
|
26
|
+
const [precontentHtml, postContent] = precontentResult instanceof Promise
|
|
27
|
+
? await precontentResult
|
|
28
|
+
: precontentResult;
|
|
29
|
+
|
|
30
|
+
// Chunk 1 — flush the full <head> shell immediately so the browser
|
|
31
|
+
// can start loading CSS / fonts / preload hints before the body arrives.
|
|
32
|
+
controller.enqueue(encoder.encode(precontentHtml));
|
|
33
|
+
|
|
34
|
+
// Chunk 2 — body HTML. getAppBody() triggers the actual renderToString
|
|
35
|
+
// now that head has been flushed. All data is cached from the preflight
|
|
36
|
+
// so this pass is fast (no async waits).
|
|
37
|
+
const bodyHtml = await getAppBody();
|
|
38
|
+
controller.enqueue(encoder.encode(`<div id="app">${bodyHtml}</div>`));
|
|
39
|
+
|
|
40
|
+
// Chunk 3 — JSON props script + post-content. Separated so the browser
|
|
41
|
+
// can parse/render the body while getFinalProps is still completing.
|
|
42
|
+
const { clientProps } = await finalize();
|
|
43
|
+
const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
|
|
44
|
+
controller.enqueue(encoder.encode(
|
|
45
|
+
`<script id="hadars" type="application/json">${scriptContent}</script>` +
|
|
46
|
+
postContent,
|
|
47
|
+
));
|
|
48
|
+
controller.close();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// Head chunk may already be sent; signal a stream error so the
|
|
51
|
+
// connection is closed cleanly rather than hanging.
|
|
52
|
+
controller.error(err);
|
|
53
|
+
}
|
|
30
54
|
},
|
|
31
55
|
});
|
|
32
|
-
|
|
33
56
|
return new Response(responseStream, {
|
|
34
57
|
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
35
58
|
status,
|
|
@@ -46,9 +69,9 @@ export async function buildSsrHtml(
|
|
|
46
69
|
bodyHtml: string,
|
|
47
70
|
clientProps: Record<string, unknown>,
|
|
48
71
|
headHtml: string,
|
|
49
|
-
getPrecontentHtml: (headHtml: string) => Promise<[string, string]>,
|
|
72
|
+
getPrecontentHtml: (headHtml: string) => [string, string] | Promise<[string, string]>,
|
|
50
73
|
): Promise<string> {
|
|
51
|
-
const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
|
|
74
|
+
const [precontentHtml, postContent] = await Promise.resolve(getPrecontentHtml(headHtml));
|
|
52
75
|
const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
|
|
53
76
|
return (
|
|
54
77
|
precontentHtml +
|
|
@@ -66,16 +89,22 @@ export const makePrecontentHtmlGetter = (htmlFilePromise: Promise<string>) => {
|
|
|
66
89
|
let preHead: string | null = null;
|
|
67
90
|
let postHead: string | null = null;
|
|
68
91
|
let postContent: string | null = null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
// Returns synchronously once the template has been loaded and parsed
|
|
93
|
+
// (every request after the first). Callers can check `instanceof Promise`
|
|
94
|
+
// to take a zero-await hot path.
|
|
95
|
+
return (headHtml: string): [string, string] | Promise<[string, string]> => {
|
|
96
|
+
if (preHead !== null) {
|
|
97
|
+
// Hot path — sync return, no Promise allocation.
|
|
98
|
+
return [preHead + headHtml + postHead!, postContent!];
|
|
99
|
+
}
|
|
100
|
+
return htmlFilePromise.then(html => {
|
|
72
101
|
const headEnd = html.indexOf(HEAD_MARKER);
|
|
73
102
|
const contentStart = html.indexOf(BODY_MARKER);
|
|
74
103
|
preHead = html.slice(0, headEnd);
|
|
75
104
|
postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
|
|
76
105
|
postContent = html.slice(contentStart + BODY_MARKER.length);
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
return [preHead + headHtml + postHead, postContent];
|
|
107
|
+
});
|
|
79
108
|
};
|
|
80
109
|
};
|
|
81
110
|
|
package/src/utils/staticFile.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
2
|
|
|
3
3
|
/** MIME type map keyed by lowercase file extension. */
|
|
4
4
|
const MIME: Record<string, string> = {
|
|
@@ -32,16 +32,11 @@ const MIME: Record<string, string> = {
|
|
|
32
32
|
* not exist or cannot be read.
|
|
33
33
|
*/
|
|
34
34
|
export async function tryServeFile(filePath: string): Promise<Response | null> {
|
|
35
|
-
try {
|
|
36
|
-
await stat(filePath);
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
35
|
try {
|
|
41
36
|
const data = await readFile(filePath);
|
|
42
37
|
const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
|
|
43
38
|
const contentType = MIME[ext] ?? 'application/octet-stream';
|
|
44
|
-
return new Response(data
|
|
39
|
+
return new Response(data as BodyInit, { headers: { 'Content-Type': contentType } });
|
|
45
40
|
} catch {
|
|
46
41
|
return null;
|
|
47
42
|
}
|