hadars 1.0.7 → 1.0.10
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/dist/{chunk-VAR5KTG3.js → chunk-QGDQJUIU.js} +3 -2
- package/dist/cli.js +187 -57
- package/dist/cloudflare.cjs +8 -4
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.js +6 -3
- package/dist/{hadars-CPplIz_z.d.cts → hadars-gkIw8CFo.d.cts} +24 -0
- package/dist/{hadars-CPplIz_z.d.ts → hadars-gkIw8CFo.d.ts} +24 -0
- package/dist/index.cjs +51 -0
- package/dist/index.d.cts +52 -3
- package/dist/index.d.ts +52 -3
- package/dist/index.js +50 -0
- package/dist/lambda.cjs +8 -4
- package/dist/lambda.d.cts +1 -1
- package/dist/lambda.d.ts +1 -1
- package/dist/lambda.js +6 -3
- package/dist/ssr-render-worker.js +31 -11
- package/package.json +1 -1
|
@@ -137,8 +137,8 @@ var ATTR = {
|
|
|
137
137
|
fetchPriority: "fetchpriority",
|
|
138
138
|
hrefLang: "hreflang"
|
|
139
139
|
};
|
|
140
|
-
function renderHeadTag(tag,
|
|
141
|
-
let attrs =
|
|
140
|
+
function renderHeadTag(tag, _id, opts, selfClose = false) {
|
|
141
|
+
let attrs = "";
|
|
142
142
|
let inner = "";
|
|
143
143
|
for (const [k, v] of Object.entries(opts)) {
|
|
144
144
|
if (k === "key" || k === "children") continue;
|
|
@@ -318,6 +318,7 @@ async function buildCacheEntry(res, ttl) {
|
|
|
318
318
|
}
|
|
319
319
|
});
|
|
320
320
|
headers.push(["content-encoding", "gzip"]);
|
|
321
|
+
headers.push(["vary", "accept-encoding"]);
|
|
321
322
|
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
322
323
|
}
|
|
323
324
|
async function serveFromEntry(entry, req) {
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,103 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
3
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
19
|
|
|
20
|
+
// src/utils/imageOptimizer.ts
|
|
21
|
+
var exports_imageOptimizer = {};
|
|
22
|
+
__export(exports_imageOptimizer, {
|
|
23
|
+
optimizeImages: () => optimizeImages
|
|
24
|
+
});
|
|
25
|
+
import fs from "node:fs/promises";
|
|
26
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
27
|
+
import path2 from "node:path";
|
|
28
|
+
async function optimizeImages(srcDir, destDir, opts = {}) {
|
|
29
|
+
const widths = opts.widths ?? [640, 1280, 1920];
|
|
30
|
+
const formats = opts.formats ?? ["webp"];
|
|
31
|
+
const quality = opts.quality ?? 80;
|
|
32
|
+
let sharp;
|
|
33
|
+
try {
|
|
34
|
+
const mod = await import("sharp");
|
|
35
|
+
sharp = mod.default ?? mod;
|
|
36
|
+
} catch {
|
|
37
|
+
console.warn('[hadars] `images` config requires "sharp" — run: npm install sharp');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!existsSync2(srcDir))
|
|
41
|
+
return;
|
|
42
|
+
const outBase = path2.join(destDir, "_images");
|
|
43
|
+
await fs.mkdir(outBase, { recursive: true });
|
|
44
|
+
await processDir(srcDir, srcDir, outBase, widths, formats, quality, sharp);
|
|
45
|
+
console.log(`[hadars] Image variants written to ${outBase}`);
|
|
46
|
+
}
|
|
47
|
+
async function processDir(baseDir, dir, outBase, widths, formats, quality, sharp) {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await Promise.all(entries.map(async (entry) => {
|
|
55
|
+
const fullPath = path2.join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
await processDir(baseDir, fullPath, outBase, widths, formats, quality, sharp);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!entry.isFile())
|
|
61
|
+
return;
|
|
62
|
+
const ext = path2.extname(entry.name).toLowerCase();
|
|
63
|
+
if (!SUPPORTED_EXTS.has(ext))
|
|
64
|
+
return;
|
|
65
|
+
const rel = path2.relative(baseDir, fullPath);
|
|
66
|
+
const relNoExt = rel.slice(0, -ext.length);
|
|
67
|
+
const outDir = path2.join(outBase, path2.dirname(rel));
|
|
68
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
69
|
+
const baseName = path2.basename(relNoExt);
|
|
70
|
+
let srcMtime = 0;
|
|
71
|
+
try {
|
|
72
|
+
srcMtime = (await fs.stat(fullPath)).mtimeMs;
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await Promise.all(widths.flatMap((width) => formats.map(async (fmt) => {
|
|
77
|
+
const outFile = path2.join(outDir, `${baseName}-${width}.${fmt}`);
|
|
78
|
+
try {
|
|
79
|
+
const outMtime = (await fs.stat(outFile)).mtimeMs;
|
|
80
|
+
if (outMtime >= srcMtime)
|
|
81
|
+
return;
|
|
82
|
+
} catch {}
|
|
83
|
+
try {
|
|
84
|
+
await sharp(fullPath).resize({ width, withoutEnlargement: true })[fmt]({ quality }).toFile(outFile);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.warn(`[hadars] Could not generate ${rel} → ${width}px.${fmt}:`, err);
|
|
87
|
+
}
|
|
88
|
+
})));
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
var SUPPORTED_EXTS;
|
|
92
|
+
var init_imageOptimizer = __esm(() => {
|
|
93
|
+
SUPPORTED_EXTS = new Set([".jpg", ".jpeg", ".png", ".tiff", ".bmp", ".webp", ".avif"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
5
96
|
// cli.ts
|
|
6
97
|
import { spawn as spawn2 } from "node:child_process";
|
|
7
98
|
|
|
8
99
|
// cli-lib.ts
|
|
9
|
-
import { existsSync as
|
|
100
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
10
101
|
import { mkdir as mkdir2, writeFile as writeFile2, unlink, readFile as readFile2 } from "node:fs/promises";
|
|
11
102
|
import { resolve, join as join2, dirname as dirname2 } from "node:path";
|
|
12
103
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -1182,8 +1273,8 @@ var ATTR = {
|
|
|
1182
1273
|
fetchPriority: "fetchpriority",
|
|
1183
1274
|
hrefLang: "hreflang"
|
|
1184
1275
|
};
|
|
1185
|
-
function renderHeadTag(tag,
|
|
1186
|
-
let attrs =
|
|
1276
|
+
function renderHeadTag(tag, _id, opts, selfClose = false) {
|
|
1277
|
+
let attrs = "";
|
|
1187
1278
|
let inner = "";
|
|
1188
1279
|
for (const [k, v] of Object.entries(opts)) {
|
|
1189
1280
|
if (k === "key" || k === "children")
|
|
@@ -1772,11 +1863,12 @@ async function tryServeFile(filePath) {
|
|
|
1772
1863
|
}
|
|
1773
1864
|
}
|
|
1774
1865
|
var _notFound = new Set;
|
|
1866
|
+
var _NOT_FOUND_MAX = 50000;
|
|
1775
1867
|
async function tryServeFileCached(filePath) {
|
|
1776
1868
|
if (_notFound.has(filePath))
|
|
1777
1869
|
return null;
|
|
1778
1870
|
const res = await tryServeFile(filePath);
|
|
1779
|
-
if (!res)
|
|
1871
|
+
if (!res && _notFound.size < _NOT_FOUND_MAX)
|
|
1780
1872
|
_notFound.add(filePath);
|
|
1781
1873
|
return res;
|
|
1782
1874
|
}
|
|
@@ -1786,8 +1878,8 @@ import { RspackDevServer } from "@rspack/dev-server";
|
|
|
1786
1878
|
import pathMod2 from "node:path";
|
|
1787
1879
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
1788
1880
|
import crypto from "node:crypto";
|
|
1789
|
-
import
|
|
1790
|
-
import { existsSync as
|
|
1881
|
+
import fs2 from "node:fs/promises";
|
|
1882
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1791
1883
|
import os from "node:os";
|
|
1792
1884
|
import { spawn } from "node:child_process";
|
|
1793
1885
|
import cluster from "node:cluster";
|
|
@@ -1812,6 +1904,7 @@ function buildSsrResponse(head, status, getAppBody, finalize, getPrecontentHtml)
|
|
|
1812
1904
|
controller.enqueue(encoder.encode(`<script id="hadars" type="application/json">${scriptContent}</script>` + postContent));
|
|
1813
1905
|
controller.close();
|
|
1814
1906
|
} catch (err) {
|
|
1907
|
+
console.error("[hadars] SSR render error:", err);
|
|
1815
1908
|
controller.error(err);
|
|
1816
1909
|
}
|
|
1817
1910
|
}
|
|
@@ -1895,6 +1988,7 @@ async function buildCacheEntry(res, ttl) {
|
|
|
1895
1988
|
}
|
|
1896
1989
|
});
|
|
1897
1990
|
headers.push(["content-encoding", "gzip"]);
|
|
1991
|
+
headers.push(["vary", "accept-encoding"]);
|
|
1898
1992
|
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
1899
1993
|
}
|
|
1900
1994
|
async function serveFromEntry(entry, req) {
|
|
@@ -1956,13 +2050,12 @@ class NodeStore {
|
|
|
1956
2050
|
if (!node.internal?.type)
|
|
1957
2051
|
throw new Error("[hadars] createNode: node.internal.type must be a non-empty string");
|
|
1958
2052
|
this.byId.set(node.id, node);
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
this.byType.set(node.internal.type, list);
|
|
2053
|
+
let typeMap = this.byType.get(node.internal.type);
|
|
2054
|
+
if (!typeMap) {
|
|
2055
|
+
typeMap = new Map;
|
|
2056
|
+
this.byType.set(node.internal.type, typeMap);
|
|
2057
|
+
}
|
|
2058
|
+
typeMap.set(node.id, node);
|
|
1966
2059
|
}
|
|
1967
2060
|
getNode(id) {
|
|
1968
2061
|
return this.byId.get(id);
|
|
@@ -1971,7 +2064,8 @@ class NodeStore {
|
|
|
1971
2064
|
return Array.from(this.byId.values());
|
|
1972
2065
|
}
|
|
1973
2066
|
getNodesByType(type) {
|
|
1974
|
-
|
|
2067
|
+
const typeMap = this.byType.get(type);
|
|
2068
|
+
return typeMap ? Array.from(typeMap.values()) : [];
|
|
1975
2069
|
}
|
|
1976
2070
|
getTypes() {
|
|
1977
2071
|
return Array.from(this.byType.keys());
|
|
@@ -2360,7 +2454,7 @@ function createGraphiqlHandler(executor) {
|
|
|
2360
2454
|
|
|
2361
2455
|
// src/build.ts
|
|
2362
2456
|
async function processHtmlTemplate(templatePath) {
|
|
2363
|
-
const html = await
|
|
2457
|
+
const html = await fs2.readFile(templatePath, "utf-8");
|
|
2364
2458
|
const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
2365
2459
|
const matches = [];
|
|
2366
2460
|
let m;
|
|
@@ -2373,7 +2467,7 @@ async function processHtmlTemplate(templatePath) {
|
|
|
2373
2467
|
const sourceHash = crypto.createHash("md5").update(html).digest("hex").slice(0, 8);
|
|
2374
2468
|
const cachedPath = pathMod2.join(HADARS_TMP_DIR, `template-${sourceHash}.html`);
|
|
2375
2469
|
try {
|
|
2376
|
-
await
|
|
2470
|
+
await fs2.access(cachedPath);
|
|
2377
2471
|
return cachedPath;
|
|
2378
2472
|
} catch {}
|
|
2379
2473
|
const { default: postcss } = await import("postcss");
|
|
@@ -2392,7 +2486,7 @@ async function processHtmlTemplate(templatePath) {
|
|
|
2392
2486
|
console.warn("[hadars] PostCSS error processing <style> block in HTML template:", err);
|
|
2393
2487
|
}
|
|
2394
2488
|
}
|
|
2395
|
-
await
|
|
2489
|
+
await fs2.writeFile(cachedPath, processedHtml);
|
|
2396
2490
|
return cachedPath;
|
|
2397
2491
|
}
|
|
2398
2492
|
|
|
@@ -2425,15 +2519,18 @@ class RenderWorkerPool {
|
|
|
2425
2519
|
const w = new this._Worker(this._workerPath, { workerData: { ssrBundlePath: this._ssrBundlePath } });
|
|
2426
2520
|
this.workerPending.set(w, new Set);
|
|
2427
2521
|
w.on("message", (msg) => {
|
|
2428
|
-
const { id, html, headHtml, status, error } = msg;
|
|
2522
|
+
const { id, html, headHtml, status, error, stack } = msg;
|
|
2429
2523
|
const p = this.pending.get(id);
|
|
2430
2524
|
if (!p)
|
|
2431
2525
|
return;
|
|
2432
2526
|
this.pending.delete(id);
|
|
2433
2527
|
this.workerPending.get(w)?.delete(id);
|
|
2434
|
-
if (error)
|
|
2435
|
-
|
|
2436
|
-
|
|
2528
|
+
if (error) {
|
|
2529
|
+
const e = new Error(error);
|
|
2530
|
+
if (stack)
|
|
2531
|
+
e.stack = stack;
|
|
2532
|
+
p.reject(e);
|
|
2533
|
+
} else
|
|
2437
2534
|
p.resolve({ html, headHtml, status });
|
|
2438
2535
|
});
|
|
2439
2536
|
w.on("error", (err) => {
|
|
@@ -2525,13 +2622,13 @@ var getSuffix = (mode) => mode === "development" ? `?v=${Date.now()}` : "";
|
|
|
2525
2622
|
var HadarsFolder = "./.hadars";
|
|
2526
2623
|
var StaticPath = `${HadarsFolder}/static`;
|
|
2527
2624
|
var HADARS_TMP_DIR = pathMod2.join(os.tmpdir(), "hadars");
|
|
2528
|
-
var ensureHadarsTmpDir = () =>
|
|
2625
|
+
var ensureHadarsTmpDir = () => fs2.mkdir(HADARS_TMP_DIR, { recursive: true });
|
|
2529
2626
|
var readReactMajor = async () => {
|
|
2530
2627
|
let dir = process.cwd();
|
|
2531
2628
|
while (true) {
|
|
2532
2629
|
try {
|
|
2533
2630
|
const pkgPath = pathMod2.join(dir, "node_modules", "react", "package.json");
|
|
2534
|
-
const pkg = JSON.parse(await
|
|
2631
|
+
const pkg = JSON.parse(await fs2.readFile(pkgPath, "utf-8"));
|
|
2535
2632
|
return parseInt(pkg.version.split(".")[0], 10);
|
|
2536
2633
|
} catch {}
|
|
2537
2634
|
const parent = pathMod2.dirname(dir);
|
|
@@ -2551,13 +2648,13 @@ var validateOptions = (options) => {
|
|
|
2551
2648
|
var resolveWorkerCmd = (packageDir2) => {
|
|
2552
2649
|
const tsPath = pathMod2.resolve(packageDir2, "ssr-watch.ts");
|
|
2553
2650
|
const jsPath = pathMod2.resolve(packageDir2, "ssr-watch.js");
|
|
2554
|
-
if (isBun &&
|
|
2651
|
+
if (isBun && existsSync3(tsPath)) {
|
|
2555
2652
|
return ["bun", tsPath];
|
|
2556
2653
|
}
|
|
2557
|
-
if (isDeno &&
|
|
2654
|
+
if (isDeno && existsSync3(tsPath)) {
|
|
2558
2655
|
return ["deno", "run", "--allow-all", tsPath];
|
|
2559
2656
|
}
|
|
2560
|
-
if (
|
|
2657
|
+
if (existsSync3(tsPath)) {
|
|
2561
2658
|
const allArgs = [...process.execArgv, process.argv[1] ?? ""];
|
|
2562
2659
|
const hasTsx = allArgs.some((a) => a.includes("tsx"));
|
|
2563
2660
|
const hasTsNode = allArgs.some((a) => a.includes("ts-node"));
|
|
@@ -2566,7 +2663,7 @@ var resolveWorkerCmd = (packageDir2) => {
|
|
|
2566
2663
|
if (hasTsNode)
|
|
2567
2664
|
return ["ts-node", tsPath];
|
|
2568
2665
|
}
|
|
2569
|
-
if (
|
|
2666
|
+
if (existsSync3(jsPath)) {
|
|
2570
2667
|
return ["node", jsPath];
|
|
2571
2668
|
}
|
|
2572
2669
|
throw new Error(`[hadars] SSR worker not found. Expected:
|
|
@@ -2575,7 +2672,7 @@ Run "npm run build:cli" to compile it, or launch hadars via a TypeScript runner:
|
|
|
2575
2672
|
npx tsx cli.ts dev`);
|
|
2576
2673
|
};
|
|
2577
2674
|
var dev = async (options) => {
|
|
2578
|
-
await
|
|
2675
|
+
await fs2.rm(HadarsFolder, { recursive: true, force: true });
|
|
2579
2676
|
let { port = 9090, baseURL = "" } = options;
|
|
2580
2677
|
console.log(`Starting Hadars on port ${port}`);
|
|
2581
2678
|
validateOptions(options);
|
|
@@ -2606,14 +2703,14 @@ var dev = async (options) => {
|
|
|
2606
2703
|
const clientScriptPath2 = pathMod2.resolve(packageDir2, "utils", "clientScript.tsx");
|
|
2607
2704
|
let clientScript = "";
|
|
2608
2705
|
try {
|
|
2609
|
-
clientScript = (await
|
|
2706
|
+
clientScript = (await fs2.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
|
|
2610
2707
|
} catch (err) {
|
|
2611
2708
|
console.error("Failed to read client script from package dist, falling back to src", err);
|
|
2612
2709
|
throw err;
|
|
2613
2710
|
}
|
|
2614
2711
|
await ensureHadarsTmpDir();
|
|
2615
2712
|
const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
|
|
2616
|
-
await
|
|
2713
|
+
await fs2.writeFile(tmpFilePath, clientScript);
|
|
2617
2714
|
let ssrBuildId = crypto.randomBytes(4).toString("hex");
|
|
2618
2715
|
let cachedSsrModule = null;
|
|
2619
2716
|
let cachedSsrBuildId = "";
|
|
@@ -2727,7 +2824,17 @@ var dev = async (options) => {
|
|
|
2727
2824
|
}
|
|
2728
2825
|
})();
|
|
2729
2826
|
const readyPromise = Promise.all([clientBuildDone, ssrBuildDone]);
|
|
2730
|
-
readyPromise.then(() => {
|
|
2827
|
+
readyPromise.then(async () => {
|
|
2828
|
+
if (options.images) {
|
|
2829
|
+
try {
|
|
2830
|
+
const { optimizeImages: optimizeImages2 } = await Promise.resolve().then(() => (init_imageOptimizer(), exports_imageOptimizer));
|
|
2831
|
+
const projectStaticDir = pathMod2.resolve(__dirname3, "static");
|
|
2832
|
+
const hadarStaticDir = pathMod2.resolve(__dirname3, StaticPath);
|
|
2833
|
+
await optimizeImages2(projectStaticDir, hadarStaticDir, options.images);
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
console.warn("[hadars] Image optimization failed in dev mode:", err);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2731
2838
|
if (stdoutReader) {
|
|
2732
2839
|
const reader = stdoutReader;
|
|
2733
2840
|
(async () => {
|
|
@@ -2762,7 +2869,7 @@ var dev = async (options) => {
|
|
|
2762
2869
|
}
|
|
2763
2870
|
} catch (e) {}
|
|
2764
2871
|
})();
|
|
2765
|
-
const getPrecontentHtml = makePrecontentHtmlGetter(readyPromise.then(() =>
|
|
2872
|
+
const getPrecontentHtml = makePrecontentHtmlGetter(readyPromise.then(() => fs2.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8")));
|
|
2766
2873
|
const projectStaticPath = pathMod2.resolve(process.cwd(), "static");
|
|
2767
2874
|
await serve(port, async (req, ctx) => {
|
|
2768
2875
|
await readyPromise;
|
|
@@ -2783,11 +2890,11 @@ var dev = async (options) => {
|
|
|
2783
2890
|
if (proxied)
|
|
2784
2891
|
return proxied;
|
|
2785
2892
|
const url = new URL(request.url);
|
|
2786
|
-
const
|
|
2787
|
-
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath,
|
|
2893
|
+
const path3 = url.pathname;
|
|
2894
|
+
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path3));
|
|
2788
2895
|
if (staticRes)
|
|
2789
2896
|
return staticRes;
|
|
2790
|
-
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath,
|
|
2897
|
+
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath, path3));
|
|
2791
2898
|
if (projectRes)
|
|
2792
2899
|
return projectRes;
|
|
2793
2900
|
const ssrComponentPath = pathMod2.join(__dirname3, HadarsFolder, SSR_FILENAME);
|
|
@@ -2842,14 +2949,14 @@ var build = async (options) => {
|
|
|
2842
2949
|
const clientScriptPath2 = pathMod2.resolve(packageDir2, "utils", "clientScript.js");
|
|
2843
2950
|
let clientScript = "";
|
|
2844
2951
|
try {
|
|
2845
|
-
clientScript = (await
|
|
2952
|
+
clientScript = (await fs2.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
|
|
2846
2953
|
} catch (err) {
|
|
2847
2954
|
const srcClientPath = pathMod2.resolve(packageDir2, "utils", "clientScript.tsx");
|
|
2848
|
-
clientScript = (await
|
|
2955
|
+
clientScript = (await fs2.readFile(srcClientPath, "utf-8")).replace("$_MOD_PATH$", entry + `?v=${Date.now()}`);
|
|
2849
2956
|
}
|
|
2850
2957
|
await ensureHadarsTmpDir();
|
|
2851
2958
|
const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
|
|
2852
|
-
await
|
|
2959
|
+
await fs2.writeFile(tmpFilePath, clientScript);
|
|
2853
2960
|
const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod2.resolve(__dirname3, options.htmlTemplate)) : undefined;
|
|
2854
2961
|
const reactMajor = await readReactMajor();
|
|
2855
2962
|
console.log("Building client and server bundles in parallel...");
|
|
@@ -2889,7 +2996,13 @@ var build = async (options) => {
|
|
|
2889
2996
|
postcssPlugins: options.postcssPlugins
|
|
2890
2997
|
})
|
|
2891
2998
|
]);
|
|
2892
|
-
await
|
|
2999
|
+
await fs2.rm(tmpFilePath);
|
|
3000
|
+
if (options.images) {
|
|
3001
|
+
const { optimizeImages: optimizeImages2 } = await Promise.resolve().then(() => (init_imageOptimizer(), exports_imageOptimizer));
|
|
3002
|
+
const projectStaticDir = pathMod2.resolve(__dirname3, "static");
|
|
3003
|
+
const hadarStaticDir = pathMod2.resolve(__dirname3, StaticPath);
|
|
3004
|
+
await optimizeImages2(projectStaticDir, hadarStaticDir, options.images);
|
|
3005
|
+
}
|
|
2893
3006
|
console.log("Build complete.");
|
|
2894
3007
|
};
|
|
2895
3008
|
var run = async (options) => {
|
|
@@ -2916,12 +3029,12 @@ var run = async (options) => {
|
|
|
2916
3029
|
const packageDir2 = pathMod2.dirname(fileURLToPath2(import.meta.url));
|
|
2917
3030
|
const workerJs = pathMod2.resolve(packageDir2, "ssr-render-worker.js");
|
|
2918
3031
|
const workerTs = pathMod2.resolve(packageDir2, "ssr-render-worker.ts");
|
|
2919
|
-
const workerFile =
|
|
3032
|
+
const workerFile = existsSync3(workerJs) ? workerJs : workerTs;
|
|
2920
3033
|
const ssrBundlePath = pathMod2.resolve(__dirname3, HadarsFolder, SSR_FILENAME);
|
|
2921
3034
|
renderPool = new RenderWorkerPool(workerFile, workers, ssrBundlePath);
|
|
2922
3035
|
console.log(`[hadars] SSR render pool: ${workers} worker threads`);
|
|
2923
3036
|
}
|
|
2924
|
-
const getPrecontentHtml = makePrecontentHtmlGetter(
|
|
3037
|
+
const getPrecontentHtml = makePrecontentHtmlGetter(fs2.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8"));
|
|
2925
3038
|
const projectStaticPath = pathMod2.resolve(process.cwd(), "static");
|
|
2926
3039
|
const componentPath = pathToFileURL(pathMod2.resolve(__dirname3, HadarsFolder, SSR_FILENAME)).href;
|
|
2927
3040
|
const ssrModulePromise = import(componentPath);
|
|
@@ -2938,14 +3051,14 @@ var run = async (options) => {
|
|
|
2938
3051
|
if (proxied)
|
|
2939
3052
|
return proxied;
|
|
2940
3053
|
const url = new URL(request.url);
|
|
2941
|
-
const
|
|
2942
|
-
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath,
|
|
3054
|
+
const path3 = url.pathname;
|
|
3055
|
+
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path3));
|
|
2943
3056
|
if (staticRes)
|
|
2944
3057
|
return staticRes;
|
|
2945
|
-
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath,
|
|
3058
|
+
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath, path3));
|
|
2946
3059
|
if (projectRes)
|
|
2947
3060
|
return projectRes;
|
|
2948
|
-
const routeClean =
|
|
3061
|
+
const routeClean = path3.replace(/(^\/|\/$)/g, "");
|
|
2949
3062
|
if (routeClean) {
|
|
2950
3063
|
const routeRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, routeClean, "index.html"));
|
|
2951
3064
|
if (routeRes)
|
|
@@ -2996,7 +3109,7 @@ var run = async (options) => {
|
|
|
2996
3109
|
};
|
|
2997
3110
|
|
|
2998
3111
|
// src/static.ts
|
|
2999
|
-
import { cp, mkdir, writeFile } from "node:fs/promises";
|
|
3112
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3000
3113
|
import { join, basename } from "node:path";
|
|
3001
3114
|
async function renderStaticSite(opts) {
|
|
3002
3115
|
const { ssrModule, htmlSource, staticSrc, paths, outputDir } = opts;
|
|
@@ -3038,12 +3151,28 @@ async function renderStaticSite(opts) {
|
|
|
3038
3151
|
});
|
|
3039
3152
|
}
|
|
3040
3153
|
}
|
|
3154
|
+
if (opts.baseURL && rendered.length > 0) {
|
|
3155
|
+
const base = opts.baseURL.replace(/\/$/, "");
|
|
3156
|
+
const urls = rendered.map((p) => ` <url><loc>${base}${p}</loc></url>`).join(`
|
|
3157
|
+
`);
|
|
3158
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3159
|
+
` + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3160
|
+
` + urls + `
|
|
3161
|
+
` + "</urlset>";
|
|
3162
|
+
await writeFile(join(outputDir, "sitemap.xml"), sitemap, "utf-8");
|
|
3163
|
+
}
|
|
3041
3164
|
const staticDest = join(outputDir, "static");
|
|
3042
3165
|
await mkdir(staticDest, { recursive: true });
|
|
3043
3166
|
await cp(staticSrc, staticDest, {
|
|
3044
3167
|
recursive: true,
|
|
3045
3168
|
filter: (src) => basename(src) !== "out.html"
|
|
3046
3169
|
});
|
|
3170
|
+
const copiedImages = join(staticDest, "_images");
|
|
3171
|
+
const imagesOut = join(outputDir, "_images");
|
|
3172
|
+
try {
|
|
3173
|
+
await cp(copiedImages, imagesOut, { recursive: true });
|
|
3174
|
+
await rm(copiedImages, { recursive: true, force: true });
|
|
3175
|
+
} catch {}
|
|
3047
3176
|
return { rendered, errors };
|
|
3048
3177
|
}
|
|
3049
3178
|
|
|
@@ -3052,7 +3181,7 @@ var SUPPORTED = ["hadars.config.js", "hadars.config.mjs", "hadars.config.cjs", "
|
|
|
3052
3181
|
function findConfig(cwd) {
|
|
3053
3182
|
for (const name of SUPPORTED) {
|
|
3054
3183
|
const p = resolve(cwd, name);
|
|
3055
|
-
if (
|
|
3184
|
+
if (existsSync4(p))
|
|
3056
3185
|
return p;
|
|
3057
3186
|
}
|
|
3058
3187
|
return null;
|
|
@@ -3128,11 +3257,11 @@ async function exportStatic(config, outputDir, cwd) {
|
|
|
3128
3257
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3129
3258
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3130
3259
|
const staticSrc = resolve(cwd, ".hadars", "static");
|
|
3131
|
-
if (!
|
|
3260
|
+
if (!existsSync4(ssrBundle)) {
|
|
3132
3261
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3133
3262
|
process.exit(1);
|
|
3134
3263
|
}
|
|
3135
|
-
if (!
|
|
3264
|
+
if (!existsSync4(outHtml)) {
|
|
3136
3265
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3137
3266
|
process.exit(1);
|
|
3138
3267
|
}
|
|
@@ -3168,12 +3297,13 @@ Source plugins require graphql-js to be installed:
|
|
|
3168
3297
|
staticSrc,
|
|
3169
3298
|
paths,
|
|
3170
3299
|
outputDir: outDir,
|
|
3171
|
-
graphql
|
|
3300
|
+
graphql,
|
|
3301
|
+
baseURL: config.baseURL
|
|
3172
3302
|
});
|
|
3173
3303
|
for (const p of rendered)
|
|
3174
3304
|
console.log(` [200] ${p}`);
|
|
3175
|
-
for (const { path:
|
|
3176
|
-
console.error(` [ERR] ${
|
|
3305
|
+
for (const { path: path3, error } of errors)
|
|
3306
|
+
console.error(` [ERR] ${path3}: ${error.message}`);
|
|
3177
3307
|
console.log(`
|
|
3178
3308
|
Exported to ${outputDir}/`);
|
|
3179
3309
|
if (errors.length > 0)
|
|
@@ -3187,11 +3317,11 @@ async function bundleCloudflare(config, configPath, outputFile, cwd) {
|
|
|
3187
3317
|
await build({ ...config, mode: "production" });
|
|
3188
3318
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3189
3319
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3190
|
-
if (!
|
|
3320
|
+
if (!existsSync4(ssrBundle)) {
|
|
3191
3321
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3192
3322
|
process.exit(1);
|
|
3193
3323
|
}
|
|
3194
|
-
if (!
|
|
3324
|
+
if (!existsSync4(outHtml)) {
|
|
3195
3325
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3196
3326
|
process.exit(1);
|
|
3197
3327
|
}
|
|
@@ -3244,11 +3374,11 @@ async function bundleLambda(config, configPath, outputFile, cwd) {
|
|
|
3244
3374
|
await build({ ...config, mode: "production" });
|
|
3245
3375
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3246
3376
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3247
|
-
if (!
|
|
3377
|
+
if (!existsSync4(ssrBundle)) {
|
|
3248
3378
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3249
3379
|
process.exit(1);
|
|
3250
3380
|
}
|
|
3251
|
-
if (!
|
|
3381
|
+
if (!existsSync4(outHtml)) {
|
|
3252
3382
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3253
3383
|
process.exit(1);
|
|
3254
3384
|
}
|
|
@@ -3654,7 +3784,7 @@ dist/
|
|
|
3654
3784
|
}
|
|
3655
3785
|
async function createProject(name, cwd) {
|
|
3656
3786
|
const dir = resolve(cwd, name);
|
|
3657
|
-
if (
|
|
3787
|
+
if (existsSync4(dir)) {
|
|
3658
3788
|
console.error(`Directory already exists: ${dir}`);
|
|
3659
3789
|
process.exit(1);
|
|
3660
3790
|
}
|
package/dist/cloudflare.cjs
CHANGED
|
@@ -1188,8 +1188,8 @@ var ATTR = {
|
|
|
1188
1188
|
fetchPriority: "fetchpriority",
|
|
1189
1189
|
hrefLang: "hreflang"
|
|
1190
1190
|
};
|
|
1191
|
-
function renderHeadTag(tag,
|
|
1192
|
-
let attrs =
|
|
1191
|
+
function renderHeadTag(tag, _id, opts, selfClose = false) {
|
|
1192
|
+
let attrs = "";
|
|
1193
1193
|
let inner = "";
|
|
1194
1194
|
for (const [k, v] of Object.entries(opts)) {
|
|
1195
1195
|
if (k === "key" || k === "children") continue;
|
|
@@ -1369,6 +1369,7 @@ async function buildCacheEntry(res, ttl) {
|
|
|
1369
1369
|
}
|
|
1370
1370
|
});
|
|
1371
1371
|
headers.push(["content-encoding", "gzip"]);
|
|
1372
|
+
headers.push(["vary", "accept-encoding"]);
|
|
1372
1373
|
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
1373
1374
|
}
|
|
1374
1375
|
async function serveFromEntry(entry, req) {
|
|
@@ -1430,15 +1431,18 @@ function createCloudflareHandler(options, bundled) {
|
|
|
1430
1431
|
if (proxied) return proxied;
|
|
1431
1432
|
try {
|
|
1432
1433
|
const { default: Component, getInitProps, getFinalProps } = ssrModule;
|
|
1434
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
1433
1435
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
1434
1436
|
document: {
|
|
1435
1437
|
body: Component,
|
|
1436
1438
|
lang: "en",
|
|
1437
1439
|
getInitProps,
|
|
1438
1440
|
getFinalProps
|
|
1439
|
-
}
|
|
1441
|
+
},
|
|
1442
|
+
singlePass: !isDataOnly,
|
|
1443
|
+
dataOnly: isDataOnly
|
|
1440
1444
|
});
|
|
1441
|
-
if (
|
|
1445
|
+
if (isDataOnly) {
|
|
1442
1446
|
const { clientProps: clientProps2 } = await finalize();
|
|
1443
1447
|
const serverData = clientProps2.__serverData ?? {};
|
|
1444
1448
|
return new Response(JSON.stringify({ serverData }), {
|
package/dist/cloudflare.d.cts
CHANGED
package/dist/cloudflare.d.ts
CHANGED
package/dist/cloudflare.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getReactResponse,
|
|
7
7
|
makePrecontentHtmlGetter,
|
|
8
8
|
parseRequest
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QGDQJUIU.js";
|
|
10
10
|
import "./chunk-6SOA2HTO.js";
|
|
11
11
|
import "./chunk-OZUZS2PD.js";
|
|
12
12
|
|
|
@@ -27,15 +27,18 @@ function createCloudflareHandler(options, bundled) {
|
|
|
27
27
|
if (proxied) return proxied;
|
|
28
28
|
try {
|
|
29
29
|
const { default: Component, getInitProps, getFinalProps } = ssrModule;
|
|
30
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
30
31
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
31
32
|
document: {
|
|
32
33
|
body: Component,
|
|
33
34
|
lang: "en",
|
|
34
35
|
getInitProps,
|
|
35
36
|
getFinalProps
|
|
36
|
-
}
|
|
37
|
+
},
|
|
38
|
+
singlePass: !isDataOnly,
|
|
39
|
+
dataOnly: isDataOnly
|
|
37
40
|
});
|
|
38
|
-
if (
|
|
41
|
+
if (isDataOnly) {
|
|
39
42
|
const { clientProps: clientProps2 } = await finalize();
|
|
40
43
|
const serverData = clientProps2.__serverData ?? {};
|
|
41
44
|
return new Response(JSON.stringify({ serverData }), {
|
|
@@ -274,6 +274,30 @@ interface HadarsOptions {
|
|
|
274
274
|
* onError: (err, req) => console.error('[myapp]', req.method, req.url, err)
|
|
275
275
|
*/
|
|
276
276
|
onError?: (err: Error, req: Request) => void | Promise<void>;
|
|
277
|
+
/**
|
|
278
|
+
* Build-time image variant generation. When set, `hadars build` and
|
|
279
|
+
* `hadars export static` scan the project's `static/` directory and produce
|
|
280
|
+
* resized variants for every raster image (JPG, PNG, WebP, AVIF, TIFF, BMP).
|
|
281
|
+
*
|
|
282
|
+
* Variants are written to `/_images/<original-path>-<width>.<format>` and
|
|
283
|
+
* served automatically by the production server. Use the `<HadarsImage>`
|
|
284
|
+
* component (exported from `hadars`) to generate `<picture>` elements that
|
|
285
|
+
* reference these variants with proper `srcset` / `sizes` attributes.
|
|
286
|
+
*
|
|
287
|
+
* Requires `sharp` to be installed as a project dependency:
|
|
288
|
+
* npm install sharp
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* images: { widths: [640, 1280, 1920], formats: ['webp', 'avif'], quality: 85 }
|
|
292
|
+
*/
|
|
293
|
+
images?: {
|
|
294
|
+
/** Pixel widths to generate per source image. Default: `[640, 1280, 1920]` */
|
|
295
|
+
widths?: number[];
|
|
296
|
+
/** Output formats. Default: `['webp']` */
|
|
297
|
+
formats?: ('webp' | 'avif')[];
|
|
298
|
+
/** Encoding quality 1–100. Default: `80` */
|
|
299
|
+
quality?: number;
|
|
300
|
+
};
|
|
277
301
|
}
|
|
278
302
|
/**
|
|
279
303
|
* A Gatsby-compatible source plugin entry, matching the format used in
|
|
@@ -274,6 +274,30 @@ interface HadarsOptions {
|
|
|
274
274
|
* onError: (err, req) => console.error('[myapp]', req.method, req.url, err)
|
|
275
275
|
*/
|
|
276
276
|
onError?: (err: Error, req: Request) => void | Promise<void>;
|
|
277
|
+
/**
|
|
278
|
+
* Build-time image variant generation. When set, `hadars build` and
|
|
279
|
+
* `hadars export static` scan the project's `static/` directory and produce
|
|
280
|
+
* resized variants for every raster image (JPG, PNG, WebP, AVIF, TIFF, BMP).
|
|
281
|
+
*
|
|
282
|
+
* Variants are written to `/_images/<original-path>-<width>.<format>` and
|
|
283
|
+
* served automatically by the production server. Use the `<HadarsImage>`
|
|
284
|
+
* component (exported from `hadars`) to generate `<picture>` elements that
|
|
285
|
+
* reference these variants with proper `srcset` / `sizes` attributes.
|
|
286
|
+
*
|
|
287
|
+
* Requires `sharp` to be installed as a project dependency:
|
|
288
|
+
* npm install sharp
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* images: { widths: [640, 1280, 1920], formats: ['webp', 'avif'], quality: 85 }
|
|
292
|
+
*/
|
|
293
|
+
images?: {
|
|
294
|
+
/** Pixel widths to generate per source image. Default: `[640, 1280, 1920]` */
|
|
295
|
+
widths?: number[];
|
|
296
|
+
/** Output formats. Default: `['webp']` */
|
|
297
|
+
formats?: ('webp' | 'avif')[];
|
|
298
|
+
/** Encoding quality 1–100. Default: `80` */
|
|
299
|
+
quality?: number;
|
|
300
|
+
};
|
|
277
301
|
}
|
|
278
302
|
/**
|
|
279
303
|
* A Gatsby-compatible source plugin entry, matching the format used in
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
HadarsHead: () => Head,
|
|
34
|
+
HadarsImage: () => HadarsImage,
|
|
34
35
|
initServerDataCache: () => initServerDataCache,
|
|
35
36
|
loadModule: () => loadModule,
|
|
36
37
|
useGraphQL: () => useGraphQL,
|
|
@@ -344,6 +345,55 @@ var Head = import_react.default.memo(({ children, status }) => {
|
|
|
344
345
|
return null;
|
|
345
346
|
});
|
|
346
347
|
|
|
348
|
+
// src/components/Image.tsx
|
|
349
|
+
var import_react2 = require("react");
|
|
350
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
351
|
+
var FORMAT_MIME = {
|
|
352
|
+
webp: "image/webp",
|
|
353
|
+
avif: "image/avif"
|
|
354
|
+
};
|
|
355
|
+
function HadarsImage({
|
|
356
|
+
src,
|
|
357
|
+
alt,
|
|
358
|
+
widths = [640, 1280, 1920],
|
|
359
|
+
formats = ["webp"],
|
|
360
|
+
sizes = "100vw",
|
|
361
|
+
loading = "lazy",
|
|
362
|
+
decoding = "async",
|
|
363
|
+
...rest
|
|
364
|
+
}) {
|
|
365
|
+
const dotIdx = src.lastIndexOf(".");
|
|
366
|
+
const srcNoExt = dotIdx !== -1 ? src.slice(0, dotIdx) : src;
|
|
367
|
+
const imgBase = "/_images" + srcNoExt;
|
|
368
|
+
const buildSrcSet = (fmt) => widths.map((w) => `${imgBase}-${w}.${fmt} ${w}w`).join(", ");
|
|
369
|
+
const orderedFormats = [...formats].sort((a, b) => {
|
|
370
|
+
const rank = { avif: 0, webp: 1 };
|
|
371
|
+
return (rank[a] ?? 2) - (rank[b] ?? 2);
|
|
372
|
+
});
|
|
373
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("picture", { children: [
|
|
374
|
+
orderedFormats.map((fmt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
375
|
+
"source",
|
|
376
|
+
{
|
|
377
|
+
type: FORMAT_MIME[fmt] ?? `image/${fmt}`,
|
|
378
|
+
srcSet: buildSrcSet(fmt),
|
|
379
|
+
sizes
|
|
380
|
+
},
|
|
381
|
+
fmt
|
|
382
|
+
)),
|
|
383
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
384
|
+
"img",
|
|
385
|
+
{
|
|
386
|
+
src,
|
|
387
|
+
alt,
|
|
388
|
+
loading,
|
|
389
|
+
decoding,
|
|
390
|
+
sizes,
|
|
391
|
+
...rest
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
] });
|
|
395
|
+
}
|
|
396
|
+
|
|
347
397
|
// src/index.tsx
|
|
348
398
|
function loadModule(path) {
|
|
349
399
|
return import(
|
|
@@ -354,6 +404,7 @@ function loadModule(path) {
|
|
|
354
404
|
// Annotate the CommonJS export names for ESM import in node:
|
|
355
405
|
0 && (module.exports = {
|
|
356
406
|
HadarsHead,
|
|
407
|
+
HadarsImage,
|
|
357
408
|
initServerDataCache,
|
|
358
409
|
loadModule,
|
|
359
410
|
useGraphQL,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as HadarsDocumentNode } from './hadars-
|
|
2
|
-
export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-
|
|
1
|
+
import { b as HadarsDocumentNode } from './hadars-gkIw8CFo.cjs';
|
|
2
|
+
export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-gkIw8CFo.cjs';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
5
|
/** Call this before hydrating to seed the client cache from the server's data.
|
|
@@ -68,6 +68,55 @@ declare const Head: React.FC<{
|
|
|
68
68
|
status?: number;
|
|
69
69
|
}>;
|
|
70
70
|
|
|
71
|
+
interface HadarsImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
72
|
+
src: string;
|
|
73
|
+
alt: string;
|
|
74
|
+
/**
|
|
75
|
+
* Pixel widths to include in the `srcset`. Must match (or be a subset of)
|
|
76
|
+
* the `images.widths` configured in `hadars.config.ts`.
|
|
77
|
+
* Default: `[640, 1280, 1920]`.
|
|
78
|
+
*/
|
|
79
|
+
widths?: number[];
|
|
80
|
+
/**
|
|
81
|
+
* Formats to offer, in preference order (most-preferred first).
|
|
82
|
+
* Browsers pick the first format they support.
|
|
83
|
+
* Default: `['webp']`.
|
|
84
|
+
*/
|
|
85
|
+
formats?: ('webp' | 'avif')[];
|
|
86
|
+
/**
|
|
87
|
+
* `sizes` attribute forwarded to both `<source>` and the fallback `<img>`.
|
|
88
|
+
* Default: `'100vw'`.
|
|
89
|
+
*/
|
|
90
|
+
sizes?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Responsive image component.
|
|
94
|
+
*
|
|
95
|
+
* Renders a `<picture>` element with `<source>` elements pointing at the
|
|
96
|
+
* build-time variants generated by `hadars build` or `hadars export static`.
|
|
97
|
+
* Variants are served from `/_images/<src-without-ext>-<width>.<format>`.
|
|
98
|
+
*
|
|
99
|
+
* Configure variant generation in `hadars.config.ts`:
|
|
100
|
+
* ```ts
|
|
101
|
+
* import type { HadarsOptions } from 'hadars';
|
|
102
|
+
* export default {
|
|
103
|
+
* images: { widths: [640, 1280, 1920], formats: ['webp', 'avif'], quality: 85 },
|
|
104
|
+
* } satisfies HadarsOptions;
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Falls back to the original `src` for browsers without `<picture>` support,
|
|
108
|
+
* and always sets `loading="lazy"` and `decoding="async"` unless overridden.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* <HadarsImage
|
|
112
|
+
* src="/photos/hero.jpg"
|
|
113
|
+
* alt="A scenic mountain view"
|
|
114
|
+
* widths={[640, 1280, 1920]}
|
|
115
|
+
* sizes="(max-width: 768px) 100vw, 50vw"
|
|
116
|
+
* />
|
|
117
|
+
*/
|
|
118
|
+
declare function HadarsImage({ src, alt, widths, formats, sizes, loading, decoding, ...rest }: HadarsImageProps): React.ReactElement;
|
|
119
|
+
|
|
71
120
|
/**
|
|
72
121
|
* Dynamically loads a module with target-aware behaviour:
|
|
73
122
|
*
|
|
@@ -92,4 +141,4 @@ declare const Head: React.FC<{
|
|
|
92
141
|
*/
|
|
93
142
|
declare function loadModule<T = any>(path: string): Promise<T>;
|
|
94
143
|
|
|
95
|
-
export { HadarsDocumentNode, Head as HadarsHead, initServerDataCache, loadModule, useGraphQL, useServerData };
|
|
144
|
+
export { HadarsDocumentNode, Head as HadarsHead, HadarsImage, type HadarsImageProps, initServerDataCache, loadModule, useGraphQL, useServerData };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as HadarsDocumentNode } from './hadars-
|
|
2
|
-
export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-
|
|
1
|
+
import { b as HadarsDocumentNode } from './hadars-gkIw8CFo.js';
|
|
2
|
+
export { G as GraphQLExecutor, c as HadarsApp, H as HadarsEntryModule, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest, i as HadarsSourceEntry, j as HadarsStaticContext } from './hadars-gkIw8CFo.js';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
5
|
/** Call this before hydrating to seed the client cache from the server's data.
|
|
@@ -68,6 +68,55 @@ declare const Head: React.FC<{
|
|
|
68
68
|
status?: number;
|
|
69
69
|
}>;
|
|
70
70
|
|
|
71
|
+
interface HadarsImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
72
|
+
src: string;
|
|
73
|
+
alt: string;
|
|
74
|
+
/**
|
|
75
|
+
* Pixel widths to include in the `srcset`. Must match (or be a subset of)
|
|
76
|
+
* the `images.widths` configured in `hadars.config.ts`.
|
|
77
|
+
* Default: `[640, 1280, 1920]`.
|
|
78
|
+
*/
|
|
79
|
+
widths?: number[];
|
|
80
|
+
/**
|
|
81
|
+
* Formats to offer, in preference order (most-preferred first).
|
|
82
|
+
* Browsers pick the first format they support.
|
|
83
|
+
* Default: `['webp']`.
|
|
84
|
+
*/
|
|
85
|
+
formats?: ('webp' | 'avif')[];
|
|
86
|
+
/**
|
|
87
|
+
* `sizes` attribute forwarded to both `<source>` and the fallback `<img>`.
|
|
88
|
+
* Default: `'100vw'`.
|
|
89
|
+
*/
|
|
90
|
+
sizes?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Responsive image component.
|
|
94
|
+
*
|
|
95
|
+
* Renders a `<picture>` element with `<source>` elements pointing at the
|
|
96
|
+
* build-time variants generated by `hadars build` or `hadars export static`.
|
|
97
|
+
* Variants are served from `/_images/<src-without-ext>-<width>.<format>`.
|
|
98
|
+
*
|
|
99
|
+
* Configure variant generation in `hadars.config.ts`:
|
|
100
|
+
* ```ts
|
|
101
|
+
* import type { HadarsOptions } from 'hadars';
|
|
102
|
+
* export default {
|
|
103
|
+
* images: { widths: [640, 1280, 1920], formats: ['webp', 'avif'], quality: 85 },
|
|
104
|
+
* } satisfies HadarsOptions;
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Falls back to the original `src` for browsers without `<picture>` support,
|
|
108
|
+
* and always sets `loading="lazy"` and `decoding="async"` unless overridden.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* <HadarsImage
|
|
112
|
+
* src="/photos/hero.jpg"
|
|
113
|
+
* alt="A scenic mountain view"
|
|
114
|
+
* widths={[640, 1280, 1920]}
|
|
115
|
+
* sizes="(max-width: 768px) 100vw, 50vw"
|
|
116
|
+
* />
|
|
117
|
+
*/
|
|
118
|
+
declare function HadarsImage({ src, alt, widths, formats, sizes, loading, decoding, ...rest }: HadarsImageProps): React.ReactElement;
|
|
119
|
+
|
|
71
120
|
/**
|
|
72
121
|
* Dynamically loads a module with target-aware behaviour:
|
|
73
122
|
*
|
|
@@ -92,4 +141,4 @@ declare const Head: React.FC<{
|
|
|
92
141
|
*/
|
|
93
142
|
declare function loadModule<T = any>(path: string): Promise<T>;
|
|
94
143
|
|
|
95
|
-
export { HadarsDocumentNode, Head as HadarsHead, initServerDataCache, loadModule, useGraphQL, useServerData };
|
|
144
|
+
export { HadarsDocumentNode, Head as HadarsHead, HadarsImage, type HadarsImageProps, initServerDataCache, loadModule, useGraphQL, useServerData };
|
package/dist/index.js
CHANGED
|
@@ -304,6 +304,55 @@ var Head = React.memo(({ children, status }) => {
|
|
|
304
304
|
return null;
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
+
// src/components/Image.tsx
|
|
308
|
+
import "react";
|
|
309
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
310
|
+
var FORMAT_MIME = {
|
|
311
|
+
webp: "image/webp",
|
|
312
|
+
avif: "image/avif"
|
|
313
|
+
};
|
|
314
|
+
function HadarsImage({
|
|
315
|
+
src,
|
|
316
|
+
alt,
|
|
317
|
+
widths = [640, 1280, 1920],
|
|
318
|
+
formats = ["webp"],
|
|
319
|
+
sizes = "100vw",
|
|
320
|
+
loading = "lazy",
|
|
321
|
+
decoding = "async",
|
|
322
|
+
...rest
|
|
323
|
+
}) {
|
|
324
|
+
const dotIdx = src.lastIndexOf(".");
|
|
325
|
+
const srcNoExt = dotIdx !== -1 ? src.slice(0, dotIdx) : src;
|
|
326
|
+
const imgBase = "/_images" + srcNoExt;
|
|
327
|
+
const buildSrcSet = (fmt) => widths.map((w) => `${imgBase}-${w}.${fmt} ${w}w`).join(", ");
|
|
328
|
+
const orderedFormats = [...formats].sort((a, b) => {
|
|
329
|
+
const rank = { avif: 0, webp: 1 };
|
|
330
|
+
return (rank[a] ?? 2) - (rank[b] ?? 2);
|
|
331
|
+
});
|
|
332
|
+
return /* @__PURE__ */ jsxs("picture", { children: [
|
|
333
|
+
orderedFormats.map((fmt) => /* @__PURE__ */ jsx(
|
|
334
|
+
"source",
|
|
335
|
+
{
|
|
336
|
+
type: FORMAT_MIME[fmt] ?? `image/${fmt}`,
|
|
337
|
+
srcSet: buildSrcSet(fmt),
|
|
338
|
+
sizes
|
|
339
|
+
},
|
|
340
|
+
fmt
|
|
341
|
+
)),
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
"img",
|
|
344
|
+
{
|
|
345
|
+
src,
|
|
346
|
+
alt,
|
|
347
|
+
loading,
|
|
348
|
+
decoding,
|
|
349
|
+
sizes,
|
|
350
|
+
...rest
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
] });
|
|
354
|
+
}
|
|
355
|
+
|
|
307
356
|
// src/index.tsx
|
|
308
357
|
function loadModule(path) {
|
|
309
358
|
return import(
|
|
@@ -313,6 +362,7 @@ function loadModule(path) {
|
|
|
313
362
|
}
|
|
314
363
|
export {
|
|
315
364
|
Head as HadarsHead,
|
|
365
|
+
HadarsImage,
|
|
316
366
|
initServerDataCache,
|
|
317
367
|
loadModule,
|
|
318
368
|
useGraphQL,
|
package/dist/lambda.cjs
CHANGED
|
@@ -1228,8 +1228,8 @@ var ATTR = {
|
|
|
1228
1228
|
fetchPriority: "fetchpriority",
|
|
1229
1229
|
hrefLang: "hreflang"
|
|
1230
1230
|
};
|
|
1231
|
-
function renderHeadTag(tag,
|
|
1232
|
-
let attrs =
|
|
1231
|
+
function renderHeadTag(tag, _id, opts, selfClose = false) {
|
|
1232
|
+
let attrs = "";
|
|
1233
1233
|
let inner = "";
|
|
1234
1234
|
for (const [k, v] of Object.entries(opts)) {
|
|
1235
1235
|
if (k === "key" || k === "children") continue;
|
|
@@ -1409,6 +1409,7 @@ async function buildCacheEntry(res, ttl) {
|
|
|
1409
1409
|
}
|
|
1410
1410
|
});
|
|
1411
1411
|
headers.push(["content-encoding", "gzip"]);
|
|
1412
|
+
headers.push(["vary", "accept-encoding"]);
|
|
1412
1413
|
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
1413
1414
|
}
|
|
1414
1415
|
async function serveFromEntry(entry, req) {
|
|
@@ -1553,15 +1554,18 @@ function createLambdaHandler(options, bundled) {
|
|
|
1553
1554
|
getInitProps,
|
|
1554
1555
|
getFinalProps
|
|
1555
1556
|
} = await getSsrModule();
|
|
1557
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
1556
1558
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
1557
1559
|
document: {
|
|
1558
1560
|
body: Component,
|
|
1559
1561
|
lang: "en",
|
|
1560
1562
|
getInitProps,
|
|
1561
1563
|
getFinalProps
|
|
1562
|
-
}
|
|
1564
|
+
},
|
|
1565
|
+
singlePass: !isDataOnly,
|
|
1566
|
+
dataOnly: isDataOnly
|
|
1563
1567
|
});
|
|
1564
|
-
if (
|
|
1568
|
+
if (isDataOnly) {
|
|
1565
1569
|
const { clientProps: clientProps2 } = await finalize();
|
|
1566
1570
|
const serverData = clientProps2.__serverData ?? {};
|
|
1567
1571
|
return new Response(JSON.stringify({ serverData }), {
|
package/dist/lambda.d.cts
CHANGED
package/dist/lambda.d.ts
CHANGED
package/dist/lambda.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getReactResponse,
|
|
7
7
|
makePrecontentHtmlGetter,
|
|
8
8
|
parseRequest
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QGDQJUIU.js";
|
|
10
10
|
import "./chunk-6SOA2HTO.js";
|
|
11
11
|
import "./chunk-OZUZS2PD.js";
|
|
12
12
|
|
|
@@ -152,15 +152,18 @@ function createLambdaHandler(options, bundled) {
|
|
|
152
152
|
getInitProps,
|
|
153
153
|
getFinalProps
|
|
154
154
|
} = await getSsrModule();
|
|
155
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
155
156
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
156
157
|
document: {
|
|
157
158
|
body: Component,
|
|
158
159
|
lang: "en",
|
|
159
160
|
getInitProps,
|
|
160
161
|
getFinalProps
|
|
161
|
-
}
|
|
162
|
+
},
|
|
163
|
+
singlePass: !isDataOnly,
|
|
164
|
+
dataOnly: isDataOnly
|
|
162
165
|
});
|
|
163
|
-
if (
|
|
166
|
+
if (isDataOnly) {
|
|
164
167
|
const { clientProps: clientProps2 } = await finalize();
|
|
165
168
|
const serverData = clientProps2.__serverData ?? {};
|
|
166
169
|
return new Response(JSON.stringify({ serverData }), {
|
|
@@ -1026,8 +1026,8 @@ var ATTR = {
|
|
|
1026
1026
|
fetchPriority: "fetchpriority",
|
|
1027
1027
|
hrefLang: "hreflang"
|
|
1028
1028
|
};
|
|
1029
|
-
function renderHeadTag(tag,
|
|
1030
|
-
let attrs =
|
|
1029
|
+
function renderHeadTag(tag, _id, opts, selfClose = false) {
|
|
1030
|
+
let attrs = "";
|
|
1031
1031
|
let inner = "";
|
|
1032
1032
|
for (const [k, v] of Object.entries(opts)) {
|
|
1033
1033
|
if (k === "key" || k === "children")
|
|
@@ -1114,15 +1114,35 @@ async function runFullLifecycle(serialReq) {
|
|
|
1114
1114
|
const html = `<div id="app">${appHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>`;
|
|
1115
1115
|
return { html, headHtml, status };
|
|
1116
1116
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1117
|
+
var _draining = false;
|
|
1118
|
+
var _queue = [];
|
|
1119
|
+
function enqueue(task) {
|
|
1120
|
+
_queue.push(task);
|
|
1121
|
+
if (!_draining)
|
|
1122
|
+
drain();
|
|
1123
|
+
}
|
|
1124
|
+
async function drain() {
|
|
1125
|
+
_draining = true;
|
|
1119
1126
|
try {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
parentPort.postMessage({ id, error: err?.message ?? String(err) });
|
|
1127
|
+
while (_queue.length > 0) {
|
|
1128
|
+
const task = _queue.shift();
|
|
1129
|
+
await task();
|
|
1130
|
+
}
|
|
1131
|
+
} finally {
|
|
1132
|
+
_draining = false;
|
|
1127
1133
|
}
|
|
1134
|
+
}
|
|
1135
|
+
parentPort.on("message", (msg) => {
|
|
1136
|
+
const { id, type, request } = msg;
|
|
1137
|
+
enqueue(async () => {
|
|
1138
|
+
try {
|
|
1139
|
+
await init();
|
|
1140
|
+
if (type !== "renderFull")
|
|
1141
|
+
return;
|
|
1142
|
+
const { html, headHtml, status } = await runFullLifecycle(request);
|
|
1143
|
+
parentPort.postMessage({ id, html, headHtml, status });
|
|
1144
|
+
} catch (err) {
|
|
1145
|
+
parentPort.postMessage({ id, error: err?.message ?? String(err), stack: err?.stack });
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1128
1148
|
});
|