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/dist/lambda.d.cts
CHANGED
package/dist/lambda.d.ts
CHANGED
package/dist/lambda.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
buildHeadHtml,
|
|
3
|
+
buildSsrHtml,
|
|
4
|
+
createProxyHandler,
|
|
5
|
+
createRenderCache,
|
|
6
|
+
getReactResponse,
|
|
7
|
+
makePrecontentHtmlGetter,
|
|
8
|
+
parseRequest
|
|
9
|
+
} from "./chunk-HWOLYLPF.js";
|
|
10
|
+
import "./chunk-LY5MTHFV.js";
|
|
11
|
+
import "./chunk-OS3V4CPN.js";
|
|
7
12
|
|
|
8
13
|
// src/lambda.ts
|
|
9
14
|
import "react";
|
|
@@ -11,120 +16,8 @@ import pathMod from "node:path";
|
|
|
11
16
|
import { pathToFileURL } from "node:url";
|
|
12
17
|
import fs from "node:fs/promises";
|
|
13
18
|
|
|
14
|
-
// src/utils/proxyHandler.tsx
|
|
15
|
-
var cloneHeaders = (headers) => {
|
|
16
|
-
return new Headers(headers);
|
|
17
|
-
};
|
|
18
|
-
var getCORSHeaders = (req) => {
|
|
19
|
-
const origin = req.headers.get("Origin") || "*";
|
|
20
|
-
return {
|
|
21
|
-
"Access-Control-Allow-Origin": origin,
|
|
22
|
-
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
23
|
-
"Access-Control-Allow-Headers": req.headers.get("Access-Control-Request-Headers") || "*",
|
|
24
|
-
"Access-Control-Allow-Credentials": "true"
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
var createProxyHandler = (options) => {
|
|
28
|
-
const { proxy, proxyCORS } = options;
|
|
29
|
-
if (!proxy) {
|
|
30
|
-
return () => void 0;
|
|
31
|
-
}
|
|
32
|
-
if (typeof proxy === "function") {
|
|
33
|
-
return async (req) => {
|
|
34
|
-
if (req.method === "OPTIONS" && options.proxyCORS) {
|
|
35
|
-
return new Response(null, {
|
|
36
|
-
status: 204,
|
|
37
|
-
headers: getCORSHeaders(req)
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
const res = await proxy(req);
|
|
41
|
-
if (res && proxyCORS) {
|
|
42
|
-
const modifiedHeaders = new Headers(res.headers);
|
|
43
|
-
Object.entries(getCORSHeaders(req)).forEach(([key, value]) => {
|
|
44
|
-
modifiedHeaders.set(key, value);
|
|
45
|
-
});
|
|
46
|
-
return new Response(res.body, {
|
|
47
|
-
status: res.status,
|
|
48
|
-
statusText: res.statusText,
|
|
49
|
-
headers: modifiedHeaders
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return res || void 0;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const proxyRules = Object.entries(proxy).sort((a, b) => b[0].length - a[0].length);
|
|
56
|
-
return async (req) => {
|
|
57
|
-
for (const [path, target] of proxyRules) {
|
|
58
|
-
if (req.pathname.startsWith(path)) {
|
|
59
|
-
if (req.method === "OPTIONS" && proxyCORS) {
|
|
60
|
-
return new Response(null, {
|
|
61
|
-
status: 204,
|
|
62
|
-
headers: getCORSHeaders(req)
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
const targetURL = new URL(target);
|
|
66
|
-
targetURL.pathname = targetURL.pathname.replace(/\/$/, "") + req.pathname.slice(path.length);
|
|
67
|
-
targetURL.search = req.search;
|
|
68
|
-
const sendHeaders = cloneHeaders(req.headers);
|
|
69
|
-
sendHeaders.set("Host", targetURL.host);
|
|
70
|
-
const hasBody = !["GET", "HEAD"].includes(req.method);
|
|
71
|
-
const proxyReq = new Request(targetURL.toString(), {
|
|
72
|
-
method: req.method,
|
|
73
|
-
headers: sendHeaders,
|
|
74
|
-
body: hasBody ? req.body : void 0,
|
|
75
|
-
redirect: "follow",
|
|
76
|
-
// Node.js (undici) requires duplex:'half' when body is a ReadableStream
|
|
77
|
-
...hasBody ? { duplex: "half" } : {}
|
|
78
|
-
});
|
|
79
|
-
const res = await fetch(proxyReq);
|
|
80
|
-
const body = await res.arrayBuffer();
|
|
81
|
-
const clonedRes = new Headers(res.headers);
|
|
82
|
-
clonedRes.delete("content-length");
|
|
83
|
-
clonedRes.delete("content-encoding");
|
|
84
|
-
if (proxyCORS) {
|
|
85
|
-
Object.entries(getCORSHeaders(req)).forEach(([key, value]) => {
|
|
86
|
-
clonedRes.set(key, value);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return new Response(body, {
|
|
90
|
-
status: res.status,
|
|
91
|
-
statusText: res.statusText,
|
|
92
|
-
headers: clonedRes
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return void 0;
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// src/utils/cookies.ts
|
|
101
|
-
var parseCookies = (cookieString) => {
|
|
102
|
-
const cookies = {};
|
|
103
|
-
if (!cookieString) {
|
|
104
|
-
return cookies;
|
|
105
|
-
}
|
|
106
|
-
const pairs = cookieString.split(";");
|
|
107
|
-
for (const pair of pairs) {
|
|
108
|
-
const index = pair.indexOf("=");
|
|
109
|
-
if (index > -1) {
|
|
110
|
-
const key = pair.slice(0, index).trim();
|
|
111
|
-
const value = pair.slice(index + 1).trim();
|
|
112
|
-
cookies[key] = decodeURIComponent(value);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return cookies;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/utils/request.tsx
|
|
119
|
-
var parseRequest = (request) => {
|
|
120
|
-
const url = new URL(request.url);
|
|
121
|
-
const cookies = request.headers.get("Cookie") || "";
|
|
122
|
-
const cookieRecord = parseCookies(cookies);
|
|
123
|
-
return Object.assign(request, { pathname: url.pathname, search: url.search, location: url.pathname + url.search, cookies: cookieRecord });
|
|
124
|
-
};
|
|
125
|
-
|
|
126
19
|
// src/utils/staticFile.ts
|
|
127
|
-
import { readFile
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
128
21
|
var MIME = {
|
|
129
22
|
html: "text/html; charset=utf-8",
|
|
130
23
|
htm: "text/html; charset=utf-8",
|
|
@@ -150,202 +43,16 @@ var MIME = {
|
|
|
150
43
|
pdf: "application/pdf"
|
|
151
44
|
};
|
|
152
45
|
async function tryServeFile(filePath) {
|
|
153
|
-
try {
|
|
154
|
-
await stat(filePath);
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
46
|
try {
|
|
159
47
|
const data = await readFile(filePath);
|
|
160
48
|
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
161
49
|
const contentType = MIME[ext] ?? "application/octet-stream";
|
|
162
|
-
return new Response(data
|
|
50
|
+
return new Response(data, { headers: { "Content-Type": contentType } });
|
|
163
51
|
} catch {
|
|
164
52
|
return null;
|
|
165
53
|
}
|
|
166
54
|
}
|
|
167
55
|
|
|
168
|
-
// src/utils/response.tsx
|
|
169
|
-
var ESC = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
170
|
-
var escAttr = (s) => s.replace(/[&<>"]/g, (c) => ESC[c] ?? c);
|
|
171
|
-
var escText = (s) => s.replace(/[&<>]/g, (c) => ESC[c] ?? c);
|
|
172
|
-
var ATTR = {
|
|
173
|
-
className: "class",
|
|
174
|
-
htmlFor: "for",
|
|
175
|
-
httpEquiv: "http-equiv",
|
|
176
|
-
charSet: "charset",
|
|
177
|
-
crossOrigin: "crossorigin",
|
|
178
|
-
noModule: "nomodule",
|
|
179
|
-
referrerPolicy: "referrerpolicy",
|
|
180
|
-
fetchPriority: "fetchpriority",
|
|
181
|
-
hrefLang: "hreflang"
|
|
182
|
-
};
|
|
183
|
-
function renderHeadTag(tag, opts, selfClose = false) {
|
|
184
|
-
let attrs = "";
|
|
185
|
-
let inner = "";
|
|
186
|
-
for (const [k, v] of Object.entries(opts)) {
|
|
187
|
-
if (k === "key" || k === "children") continue;
|
|
188
|
-
if (k === "dangerouslySetInnerHTML") {
|
|
189
|
-
inner = v.__html ?? "";
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
const attr = ATTR[k] ?? k;
|
|
193
|
-
if (v === true) attrs += ` ${attr}`;
|
|
194
|
-
else if (v !== false && v != null) attrs += ` ${attr}="${escAttr(String(v))}"`;
|
|
195
|
-
}
|
|
196
|
-
return selfClose ? `<${tag}${attrs}>` : `<${tag}${attrs}>${inner}</${tag}>`;
|
|
197
|
-
}
|
|
198
|
-
var getHeadHtml = (seoData) => {
|
|
199
|
-
let html = `<title>${escText(seoData.title ?? "")}</title>`;
|
|
200
|
-
for (const opts of Object.values(seoData.meta))
|
|
201
|
-
html += renderHeadTag("meta", opts, true);
|
|
202
|
-
for (const opts of Object.values(seoData.link))
|
|
203
|
-
html += renderHeadTag("link", opts, true);
|
|
204
|
-
for (const opts of Object.values(seoData.style))
|
|
205
|
-
html += renderHeadTag("style", opts);
|
|
206
|
-
for (const opts of Object.values(seoData.script))
|
|
207
|
-
html += renderHeadTag("script", opts);
|
|
208
|
-
return html;
|
|
209
|
-
};
|
|
210
|
-
var getReactResponse = async (req, opts) => {
|
|
211
|
-
const App = opts.document.body;
|
|
212
|
-
const { getInitProps, getFinalProps } = opts.document;
|
|
213
|
-
const context = {
|
|
214
|
-
head: { title: "Hadars App", meta: {}, link: {}, style: {}, script: {}, status: 200 }
|
|
215
|
-
};
|
|
216
|
-
let props = {
|
|
217
|
-
...getInitProps ? await getInitProps(req) : {},
|
|
218
|
-
location: req.location,
|
|
219
|
-
context
|
|
220
|
-
};
|
|
221
|
-
const unsuspend = { cache: /* @__PURE__ */ new Map() };
|
|
222
|
-
globalThis.__hadarsUnsuspend = unsuspend;
|
|
223
|
-
let bodyHtml;
|
|
224
|
-
try {
|
|
225
|
-
bodyHtml = await renderToString(createElement(App, props));
|
|
226
|
-
} finally {
|
|
227
|
-
globalThis.__hadarsUnsuspend = null;
|
|
228
|
-
}
|
|
229
|
-
const { context: _, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
|
|
230
|
-
const serverData = {};
|
|
231
|
-
for (const [key, entry] of unsuspend.cache) {
|
|
232
|
-
if (entry.status === "fulfilled") serverData[key] = entry.value;
|
|
233
|
-
}
|
|
234
|
-
const clientProps = {
|
|
235
|
-
...restProps,
|
|
236
|
-
location: req.location,
|
|
237
|
-
...Object.keys(serverData).length > 0 ? { __serverData: serverData } : {}
|
|
238
|
-
};
|
|
239
|
-
return {
|
|
240
|
-
bodyHtml,
|
|
241
|
-
clientProps,
|
|
242
|
-
status: context.head.status,
|
|
243
|
-
headHtml: getHeadHtml(context.head)
|
|
244
|
-
};
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// src/utils/ssrHandler.ts
|
|
248
|
-
var HEAD_MARKER = '<meta name="HADARS_HEAD">';
|
|
249
|
-
var BODY_MARKER = '<meta name="HADARS_BODY">';
|
|
250
|
-
var encoder = new TextEncoder();
|
|
251
|
-
async function buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml) {
|
|
252
|
-
const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
|
|
253
|
-
const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
|
|
254
|
-
return precontentHtml + `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent;
|
|
255
|
-
}
|
|
256
|
-
var makePrecontentHtmlGetter = (htmlFilePromise) => {
|
|
257
|
-
let preHead = null;
|
|
258
|
-
let postHead = null;
|
|
259
|
-
let postContent = null;
|
|
260
|
-
return async (headHtml) => {
|
|
261
|
-
if (preHead === null || postHead === null || postContent === null) {
|
|
262
|
-
const html = await htmlFilePromise;
|
|
263
|
-
const headEnd = html.indexOf(HEAD_MARKER);
|
|
264
|
-
const contentStart = html.indexOf(BODY_MARKER);
|
|
265
|
-
preHead = html.slice(0, headEnd);
|
|
266
|
-
postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
|
|
267
|
-
postContent = html.slice(contentStart + BODY_MARKER.length);
|
|
268
|
-
}
|
|
269
|
-
return [preHead + headHtml + postHead, postContent];
|
|
270
|
-
};
|
|
271
|
-
};
|
|
272
|
-
async function transformStream(data, stream) {
|
|
273
|
-
const writer = stream.writable.getWriter();
|
|
274
|
-
writer.write(data);
|
|
275
|
-
writer.close();
|
|
276
|
-
const chunks = [];
|
|
277
|
-
const reader = stream.readable.getReader();
|
|
278
|
-
while (true) {
|
|
279
|
-
const { done, value } = await reader.read();
|
|
280
|
-
if (done) break;
|
|
281
|
-
chunks.push(value);
|
|
282
|
-
}
|
|
283
|
-
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
284
|
-
const out = new Uint8Array(total);
|
|
285
|
-
let offset = 0;
|
|
286
|
-
for (const c of chunks) {
|
|
287
|
-
out.set(c, offset);
|
|
288
|
-
offset += c.length;
|
|
289
|
-
}
|
|
290
|
-
return out;
|
|
291
|
-
}
|
|
292
|
-
var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
|
|
293
|
-
var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
|
|
294
|
-
async function buildCacheEntry(res, ttl) {
|
|
295
|
-
const buf = await res.arrayBuffer();
|
|
296
|
-
const body = await gzipCompress(new Uint8Array(buf));
|
|
297
|
-
const headers = [];
|
|
298
|
-
res.headers.forEach((v, k) => {
|
|
299
|
-
if (k.toLowerCase() !== "content-encoding" && k.toLowerCase() !== "content-length") {
|
|
300
|
-
headers.push([k, v]);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
headers.push(["content-encoding", "gzip"]);
|
|
304
|
-
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
305
|
-
}
|
|
306
|
-
async function serveFromEntry(entry, req) {
|
|
307
|
-
const accept = req.headers.get("Accept-Encoding") ?? "";
|
|
308
|
-
if (accept.includes("gzip")) {
|
|
309
|
-
return new Response(entry.body.buffer, { status: entry.status, headers: entry.headers });
|
|
310
|
-
}
|
|
311
|
-
const plain = await gzipDecompress(entry.body);
|
|
312
|
-
const headers = entry.headers.filter(([k]) => k.toLowerCase() !== "content-encoding");
|
|
313
|
-
return new Response(plain.buffer, { status: entry.status, headers });
|
|
314
|
-
}
|
|
315
|
-
function createRenderCache(opts, handler) {
|
|
316
|
-
const store = /* @__PURE__ */ new Map();
|
|
317
|
-
const inFlight = /* @__PURE__ */ new Map();
|
|
318
|
-
return async (req, ctx) => {
|
|
319
|
-
const hadarsReq = parseRequest(req);
|
|
320
|
-
const cacheOpts = await opts(hadarsReq);
|
|
321
|
-
const key = cacheOpts?.key ?? null;
|
|
322
|
-
if (key != null) {
|
|
323
|
-
const entry = store.get(key);
|
|
324
|
-
if (entry) {
|
|
325
|
-
const expired = entry.expiresAt != null && Date.now() >= entry.expiresAt;
|
|
326
|
-
if (!expired) return serveFromEntry(entry, req);
|
|
327
|
-
store.delete(key);
|
|
328
|
-
}
|
|
329
|
-
let flight = inFlight.get(key);
|
|
330
|
-
if (!flight) {
|
|
331
|
-
const ttl = cacheOpts?.ttl;
|
|
332
|
-
flight = handler(new Request(req), ctx).then(async (res) => {
|
|
333
|
-
if (!res || res.status < 200 || res.status >= 300 || res.headers.has("set-cookie")) {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
const newEntry2 = await buildCacheEntry(res, ttl);
|
|
337
|
-
store.set(key, newEntry2);
|
|
338
|
-
return newEntry2;
|
|
339
|
-
}).catch(() => null).finally(() => inFlight.delete(key));
|
|
340
|
-
inFlight.set(key, flight);
|
|
341
|
-
}
|
|
342
|
-
const newEntry = await flight;
|
|
343
|
-
if (newEntry) return serveFromEntry(newEntry, req);
|
|
344
|
-
}
|
|
345
|
-
return handler(req, ctx);
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
56
|
// src/lambda.ts
|
|
350
57
|
function eventToRequest(event) {
|
|
351
58
|
let method;
|
|
@@ -451,7 +158,7 @@ function createLambdaHandler(options, bundled) {
|
|
|
451
158
|
getInitProps,
|
|
452
159
|
getFinalProps
|
|
453
160
|
} = await getSsrModule();
|
|
454
|
-
const {
|
|
161
|
+
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
455
162
|
document: {
|
|
456
163
|
body: Component,
|
|
457
164
|
lang: "en",
|
|
@@ -460,12 +167,16 @@ function createLambdaHandler(options, bundled) {
|
|
|
460
167
|
}
|
|
461
168
|
});
|
|
462
169
|
if (request.headers.get("Accept") === "application/json") {
|
|
463
|
-
const
|
|
170
|
+
const { clientProps: clientProps2 } = await finalize();
|
|
171
|
+
const serverData = clientProps2.__serverData ?? {};
|
|
464
172
|
return new Response(JSON.stringify({ serverData }), {
|
|
465
173
|
status,
|
|
466
174
|
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
467
175
|
});
|
|
468
176
|
}
|
|
177
|
+
const bodyHtml = await getAppBody();
|
|
178
|
+
const { clientProps } = await finalize();
|
|
179
|
+
const headHtml = buildHeadHtml(head);
|
|
469
180
|
const html = await buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml);
|
|
470
181
|
return new Response(html, {
|
|
471
182
|
status,
|