hadars 1.0.9 → 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-B4NE4AIT.js → chunk-QGDQJUIU.js} +2 -2
- package/dist/cli.js +174 -56
- package/dist/cloudflare.cjs +7 -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 +7 -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;
|
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
|
}
|
|
@@ -1957,13 +2050,12 @@ class NodeStore {
|
|
|
1957
2050
|
if (!node.internal?.type)
|
|
1958
2051
|
throw new Error("[hadars] createNode: node.internal.type must be a non-empty string");
|
|
1959
2052
|
this.byId.set(node.id, node);
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
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);
|
|
1967
2059
|
}
|
|
1968
2060
|
getNode(id) {
|
|
1969
2061
|
return this.byId.get(id);
|
|
@@ -1972,7 +2064,8 @@ class NodeStore {
|
|
|
1972
2064
|
return Array.from(this.byId.values());
|
|
1973
2065
|
}
|
|
1974
2066
|
getNodesByType(type) {
|
|
1975
|
-
|
|
2067
|
+
const typeMap = this.byType.get(type);
|
|
2068
|
+
return typeMap ? Array.from(typeMap.values()) : [];
|
|
1976
2069
|
}
|
|
1977
2070
|
getTypes() {
|
|
1978
2071
|
return Array.from(this.byType.keys());
|
|
@@ -2361,7 +2454,7 @@ function createGraphiqlHandler(executor) {
|
|
|
2361
2454
|
|
|
2362
2455
|
// src/build.ts
|
|
2363
2456
|
async function processHtmlTemplate(templatePath) {
|
|
2364
|
-
const html = await
|
|
2457
|
+
const html = await fs2.readFile(templatePath, "utf-8");
|
|
2365
2458
|
const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
2366
2459
|
const matches = [];
|
|
2367
2460
|
let m;
|
|
@@ -2374,7 +2467,7 @@ async function processHtmlTemplate(templatePath) {
|
|
|
2374
2467
|
const sourceHash = crypto.createHash("md5").update(html).digest("hex").slice(0, 8);
|
|
2375
2468
|
const cachedPath = pathMod2.join(HADARS_TMP_DIR, `template-${sourceHash}.html`);
|
|
2376
2469
|
try {
|
|
2377
|
-
await
|
|
2470
|
+
await fs2.access(cachedPath);
|
|
2378
2471
|
return cachedPath;
|
|
2379
2472
|
} catch {}
|
|
2380
2473
|
const { default: postcss } = await import("postcss");
|
|
@@ -2393,7 +2486,7 @@ async function processHtmlTemplate(templatePath) {
|
|
|
2393
2486
|
console.warn("[hadars] PostCSS error processing <style> block in HTML template:", err);
|
|
2394
2487
|
}
|
|
2395
2488
|
}
|
|
2396
|
-
await
|
|
2489
|
+
await fs2.writeFile(cachedPath, processedHtml);
|
|
2397
2490
|
return cachedPath;
|
|
2398
2491
|
}
|
|
2399
2492
|
|
|
@@ -2426,15 +2519,18 @@ class RenderWorkerPool {
|
|
|
2426
2519
|
const w = new this._Worker(this._workerPath, { workerData: { ssrBundlePath: this._ssrBundlePath } });
|
|
2427
2520
|
this.workerPending.set(w, new Set);
|
|
2428
2521
|
w.on("message", (msg) => {
|
|
2429
|
-
const { id, html, headHtml, status, error } = msg;
|
|
2522
|
+
const { id, html, headHtml, status, error, stack } = msg;
|
|
2430
2523
|
const p = this.pending.get(id);
|
|
2431
2524
|
if (!p)
|
|
2432
2525
|
return;
|
|
2433
2526
|
this.pending.delete(id);
|
|
2434
2527
|
this.workerPending.get(w)?.delete(id);
|
|
2435
|
-
if (error)
|
|
2436
|
-
|
|
2437
|
-
|
|
2528
|
+
if (error) {
|
|
2529
|
+
const e = new Error(error);
|
|
2530
|
+
if (stack)
|
|
2531
|
+
e.stack = stack;
|
|
2532
|
+
p.reject(e);
|
|
2533
|
+
} else
|
|
2438
2534
|
p.resolve({ html, headHtml, status });
|
|
2439
2535
|
});
|
|
2440
2536
|
w.on("error", (err) => {
|
|
@@ -2526,13 +2622,13 @@ var getSuffix = (mode) => mode === "development" ? `?v=${Date.now()}` : "";
|
|
|
2526
2622
|
var HadarsFolder = "./.hadars";
|
|
2527
2623
|
var StaticPath = `${HadarsFolder}/static`;
|
|
2528
2624
|
var HADARS_TMP_DIR = pathMod2.join(os.tmpdir(), "hadars");
|
|
2529
|
-
var ensureHadarsTmpDir = () =>
|
|
2625
|
+
var ensureHadarsTmpDir = () => fs2.mkdir(HADARS_TMP_DIR, { recursive: true });
|
|
2530
2626
|
var readReactMajor = async () => {
|
|
2531
2627
|
let dir = process.cwd();
|
|
2532
2628
|
while (true) {
|
|
2533
2629
|
try {
|
|
2534
2630
|
const pkgPath = pathMod2.join(dir, "node_modules", "react", "package.json");
|
|
2535
|
-
const pkg = JSON.parse(await
|
|
2631
|
+
const pkg = JSON.parse(await fs2.readFile(pkgPath, "utf-8"));
|
|
2536
2632
|
return parseInt(pkg.version.split(".")[0], 10);
|
|
2537
2633
|
} catch {}
|
|
2538
2634
|
const parent = pathMod2.dirname(dir);
|
|
@@ -2552,13 +2648,13 @@ var validateOptions = (options) => {
|
|
|
2552
2648
|
var resolveWorkerCmd = (packageDir2) => {
|
|
2553
2649
|
const tsPath = pathMod2.resolve(packageDir2, "ssr-watch.ts");
|
|
2554
2650
|
const jsPath = pathMod2.resolve(packageDir2, "ssr-watch.js");
|
|
2555
|
-
if (isBun &&
|
|
2651
|
+
if (isBun && existsSync3(tsPath)) {
|
|
2556
2652
|
return ["bun", tsPath];
|
|
2557
2653
|
}
|
|
2558
|
-
if (isDeno &&
|
|
2654
|
+
if (isDeno && existsSync3(tsPath)) {
|
|
2559
2655
|
return ["deno", "run", "--allow-all", tsPath];
|
|
2560
2656
|
}
|
|
2561
|
-
if (
|
|
2657
|
+
if (existsSync3(tsPath)) {
|
|
2562
2658
|
const allArgs = [...process.execArgv, process.argv[1] ?? ""];
|
|
2563
2659
|
const hasTsx = allArgs.some((a) => a.includes("tsx"));
|
|
2564
2660
|
const hasTsNode = allArgs.some((a) => a.includes("ts-node"));
|
|
@@ -2567,7 +2663,7 @@ var resolveWorkerCmd = (packageDir2) => {
|
|
|
2567
2663
|
if (hasTsNode)
|
|
2568
2664
|
return ["ts-node", tsPath];
|
|
2569
2665
|
}
|
|
2570
|
-
if (
|
|
2666
|
+
if (existsSync3(jsPath)) {
|
|
2571
2667
|
return ["node", jsPath];
|
|
2572
2668
|
}
|
|
2573
2669
|
throw new Error(`[hadars] SSR worker not found. Expected:
|
|
@@ -2576,7 +2672,7 @@ Run "npm run build:cli" to compile it, or launch hadars via a TypeScript runner:
|
|
|
2576
2672
|
npx tsx cli.ts dev`);
|
|
2577
2673
|
};
|
|
2578
2674
|
var dev = async (options) => {
|
|
2579
|
-
await
|
|
2675
|
+
await fs2.rm(HadarsFolder, { recursive: true, force: true });
|
|
2580
2676
|
let { port = 9090, baseURL = "" } = options;
|
|
2581
2677
|
console.log(`Starting Hadars on port ${port}`);
|
|
2582
2678
|
validateOptions(options);
|
|
@@ -2607,14 +2703,14 @@ var dev = async (options) => {
|
|
|
2607
2703
|
const clientScriptPath2 = pathMod2.resolve(packageDir2, "utils", "clientScript.tsx");
|
|
2608
2704
|
let clientScript = "";
|
|
2609
2705
|
try {
|
|
2610
|
-
clientScript = (await
|
|
2706
|
+
clientScript = (await fs2.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
|
|
2611
2707
|
} catch (err) {
|
|
2612
2708
|
console.error("Failed to read client script from package dist, falling back to src", err);
|
|
2613
2709
|
throw err;
|
|
2614
2710
|
}
|
|
2615
2711
|
await ensureHadarsTmpDir();
|
|
2616
2712
|
const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
|
|
2617
|
-
await
|
|
2713
|
+
await fs2.writeFile(tmpFilePath, clientScript);
|
|
2618
2714
|
let ssrBuildId = crypto.randomBytes(4).toString("hex");
|
|
2619
2715
|
let cachedSsrModule = null;
|
|
2620
2716
|
let cachedSsrBuildId = "";
|
|
@@ -2728,7 +2824,17 @@ var dev = async (options) => {
|
|
|
2728
2824
|
}
|
|
2729
2825
|
})();
|
|
2730
2826
|
const readyPromise = Promise.all([clientBuildDone, ssrBuildDone]);
|
|
2731
|
-
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
|
+
}
|
|
2732
2838
|
if (stdoutReader) {
|
|
2733
2839
|
const reader = stdoutReader;
|
|
2734
2840
|
(async () => {
|
|
@@ -2763,7 +2869,7 @@ var dev = async (options) => {
|
|
|
2763
2869
|
}
|
|
2764
2870
|
} catch (e) {}
|
|
2765
2871
|
})();
|
|
2766
|
-
const getPrecontentHtml = makePrecontentHtmlGetter(readyPromise.then(() =>
|
|
2872
|
+
const getPrecontentHtml = makePrecontentHtmlGetter(readyPromise.then(() => fs2.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8")));
|
|
2767
2873
|
const projectStaticPath = pathMod2.resolve(process.cwd(), "static");
|
|
2768
2874
|
await serve(port, async (req, ctx) => {
|
|
2769
2875
|
await readyPromise;
|
|
@@ -2784,11 +2890,11 @@ var dev = async (options) => {
|
|
|
2784
2890
|
if (proxied)
|
|
2785
2891
|
return proxied;
|
|
2786
2892
|
const url = new URL(request.url);
|
|
2787
|
-
const
|
|
2788
|
-
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath,
|
|
2893
|
+
const path3 = url.pathname;
|
|
2894
|
+
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path3));
|
|
2789
2895
|
if (staticRes)
|
|
2790
2896
|
return staticRes;
|
|
2791
|
-
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath,
|
|
2897
|
+
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath, path3));
|
|
2792
2898
|
if (projectRes)
|
|
2793
2899
|
return projectRes;
|
|
2794
2900
|
const ssrComponentPath = pathMod2.join(__dirname3, HadarsFolder, SSR_FILENAME);
|
|
@@ -2843,14 +2949,14 @@ var build = async (options) => {
|
|
|
2843
2949
|
const clientScriptPath2 = pathMod2.resolve(packageDir2, "utils", "clientScript.js");
|
|
2844
2950
|
let clientScript = "";
|
|
2845
2951
|
try {
|
|
2846
|
-
clientScript = (await
|
|
2952
|
+
clientScript = (await fs2.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
|
|
2847
2953
|
} catch (err) {
|
|
2848
2954
|
const srcClientPath = pathMod2.resolve(packageDir2, "utils", "clientScript.tsx");
|
|
2849
|
-
clientScript = (await
|
|
2955
|
+
clientScript = (await fs2.readFile(srcClientPath, "utf-8")).replace("$_MOD_PATH$", entry + `?v=${Date.now()}`);
|
|
2850
2956
|
}
|
|
2851
2957
|
await ensureHadarsTmpDir();
|
|
2852
2958
|
const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
|
|
2853
|
-
await
|
|
2959
|
+
await fs2.writeFile(tmpFilePath, clientScript);
|
|
2854
2960
|
const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod2.resolve(__dirname3, options.htmlTemplate)) : undefined;
|
|
2855
2961
|
const reactMajor = await readReactMajor();
|
|
2856
2962
|
console.log("Building client and server bundles in parallel...");
|
|
@@ -2890,7 +2996,13 @@ var build = async (options) => {
|
|
|
2890
2996
|
postcssPlugins: options.postcssPlugins
|
|
2891
2997
|
})
|
|
2892
2998
|
]);
|
|
2893
|
-
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
|
+
}
|
|
2894
3006
|
console.log("Build complete.");
|
|
2895
3007
|
};
|
|
2896
3008
|
var run = async (options) => {
|
|
@@ -2917,12 +3029,12 @@ var run = async (options) => {
|
|
|
2917
3029
|
const packageDir2 = pathMod2.dirname(fileURLToPath2(import.meta.url));
|
|
2918
3030
|
const workerJs = pathMod2.resolve(packageDir2, "ssr-render-worker.js");
|
|
2919
3031
|
const workerTs = pathMod2.resolve(packageDir2, "ssr-render-worker.ts");
|
|
2920
|
-
const workerFile =
|
|
3032
|
+
const workerFile = existsSync3(workerJs) ? workerJs : workerTs;
|
|
2921
3033
|
const ssrBundlePath = pathMod2.resolve(__dirname3, HadarsFolder, SSR_FILENAME);
|
|
2922
3034
|
renderPool = new RenderWorkerPool(workerFile, workers, ssrBundlePath);
|
|
2923
3035
|
console.log(`[hadars] SSR render pool: ${workers} worker threads`);
|
|
2924
3036
|
}
|
|
2925
|
-
const getPrecontentHtml = makePrecontentHtmlGetter(
|
|
3037
|
+
const getPrecontentHtml = makePrecontentHtmlGetter(fs2.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8"));
|
|
2926
3038
|
const projectStaticPath = pathMod2.resolve(process.cwd(), "static");
|
|
2927
3039
|
const componentPath = pathToFileURL(pathMod2.resolve(__dirname3, HadarsFolder, SSR_FILENAME)).href;
|
|
2928
3040
|
const ssrModulePromise = import(componentPath);
|
|
@@ -2939,14 +3051,14 @@ var run = async (options) => {
|
|
|
2939
3051
|
if (proxied)
|
|
2940
3052
|
return proxied;
|
|
2941
3053
|
const url = new URL(request.url);
|
|
2942
|
-
const
|
|
2943
|
-
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath,
|
|
3054
|
+
const path3 = url.pathname;
|
|
3055
|
+
const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path3));
|
|
2944
3056
|
if (staticRes)
|
|
2945
3057
|
return staticRes;
|
|
2946
|
-
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath,
|
|
3058
|
+
const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath, path3));
|
|
2947
3059
|
if (projectRes)
|
|
2948
3060
|
return projectRes;
|
|
2949
|
-
const routeClean =
|
|
3061
|
+
const routeClean = path3.replace(/(^\/|\/$)/g, "");
|
|
2950
3062
|
if (routeClean) {
|
|
2951
3063
|
const routeRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, routeClean, "index.html"));
|
|
2952
3064
|
if (routeRes)
|
|
@@ -2997,7 +3109,7 @@ var run = async (options) => {
|
|
|
2997
3109
|
};
|
|
2998
3110
|
|
|
2999
3111
|
// src/static.ts
|
|
3000
|
-
import { cp, mkdir, writeFile } from "node:fs/promises";
|
|
3112
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3001
3113
|
import { join, basename } from "node:path";
|
|
3002
3114
|
async function renderStaticSite(opts) {
|
|
3003
3115
|
const { ssrModule, htmlSource, staticSrc, paths, outputDir } = opts;
|
|
@@ -3055,6 +3167,12 @@ async function renderStaticSite(opts) {
|
|
|
3055
3167
|
recursive: true,
|
|
3056
3168
|
filter: (src) => basename(src) !== "out.html"
|
|
3057
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 {}
|
|
3058
3176
|
return { rendered, errors };
|
|
3059
3177
|
}
|
|
3060
3178
|
|
|
@@ -3063,7 +3181,7 @@ var SUPPORTED = ["hadars.config.js", "hadars.config.mjs", "hadars.config.cjs", "
|
|
|
3063
3181
|
function findConfig(cwd) {
|
|
3064
3182
|
for (const name of SUPPORTED) {
|
|
3065
3183
|
const p = resolve(cwd, name);
|
|
3066
|
-
if (
|
|
3184
|
+
if (existsSync4(p))
|
|
3067
3185
|
return p;
|
|
3068
3186
|
}
|
|
3069
3187
|
return null;
|
|
@@ -3139,11 +3257,11 @@ async function exportStatic(config, outputDir, cwd) {
|
|
|
3139
3257
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3140
3258
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3141
3259
|
const staticSrc = resolve(cwd, ".hadars", "static");
|
|
3142
|
-
if (!
|
|
3260
|
+
if (!existsSync4(ssrBundle)) {
|
|
3143
3261
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3144
3262
|
process.exit(1);
|
|
3145
3263
|
}
|
|
3146
|
-
if (!
|
|
3264
|
+
if (!existsSync4(outHtml)) {
|
|
3147
3265
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3148
3266
|
process.exit(1);
|
|
3149
3267
|
}
|
|
@@ -3184,8 +3302,8 @@ Source plugins require graphql-js to be installed:
|
|
|
3184
3302
|
});
|
|
3185
3303
|
for (const p of rendered)
|
|
3186
3304
|
console.log(` [200] ${p}`);
|
|
3187
|
-
for (const { path:
|
|
3188
|
-
console.error(` [ERR] ${
|
|
3305
|
+
for (const { path: path3, error } of errors)
|
|
3306
|
+
console.error(` [ERR] ${path3}: ${error.message}`);
|
|
3189
3307
|
console.log(`
|
|
3190
3308
|
Exported to ${outputDir}/`);
|
|
3191
3309
|
if (errors.length > 0)
|
|
@@ -3199,11 +3317,11 @@ async function bundleCloudflare(config, configPath, outputFile, cwd) {
|
|
|
3199
3317
|
await build({ ...config, mode: "production" });
|
|
3200
3318
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3201
3319
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3202
|
-
if (!
|
|
3320
|
+
if (!existsSync4(ssrBundle)) {
|
|
3203
3321
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3204
3322
|
process.exit(1);
|
|
3205
3323
|
}
|
|
3206
|
-
if (!
|
|
3324
|
+
if (!existsSync4(outHtml)) {
|
|
3207
3325
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3208
3326
|
process.exit(1);
|
|
3209
3327
|
}
|
|
@@ -3256,11 +3374,11 @@ async function bundleLambda(config, configPath, outputFile, cwd) {
|
|
|
3256
3374
|
await build({ ...config, mode: "production" });
|
|
3257
3375
|
const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
|
|
3258
3376
|
const outHtml = resolve(cwd, ".hadars", "static", "out.html");
|
|
3259
|
-
if (!
|
|
3377
|
+
if (!existsSync4(ssrBundle)) {
|
|
3260
3378
|
console.error(`SSR bundle not found: ${ssrBundle}`);
|
|
3261
3379
|
process.exit(1);
|
|
3262
3380
|
}
|
|
3263
|
-
if (!
|
|
3381
|
+
if (!existsSync4(outHtml)) {
|
|
3264
3382
|
console.error(`HTML template not found: ${outHtml}`);
|
|
3265
3383
|
process.exit(1);
|
|
3266
3384
|
}
|
|
@@ -3666,7 +3784,7 @@ dist/
|
|
|
3666
3784
|
}
|
|
3667
3785
|
async function createProject(name, cwd) {
|
|
3668
3786
|
const dir = resolve(cwd, name);
|
|
3669
|
-
if (
|
|
3787
|
+
if (existsSync4(dir)) {
|
|
3670
3788
|
console.error(`Directory already exists: ${dir}`);
|
|
3671
3789
|
process.exit(1);
|
|
3672
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;
|
|
@@ -1431,15 +1431,18 @@ function createCloudflareHandler(options, bundled) {
|
|
|
1431
1431
|
if (proxied) return proxied;
|
|
1432
1432
|
try {
|
|
1433
1433
|
const { default: Component, getInitProps, getFinalProps } = ssrModule;
|
|
1434
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
1434
1435
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
1435
1436
|
document: {
|
|
1436
1437
|
body: Component,
|
|
1437
1438
|
lang: "en",
|
|
1438
1439
|
getInitProps,
|
|
1439
1440
|
getFinalProps
|
|
1440
|
-
}
|
|
1441
|
+
},
|
|
1442
|
+
singlePass: !isDataOnly,
|
|
1443
|
+
dataOnly: isDataOnly
|
|
1441
1444
|
});
|
|
1442
|
-
if (
|
|
1445
|
+
if (isDataOnly) {
|
|
1443
1446
|
const { clientProps: clientProps2 } = await finalize();
|
|
1444
1447
|
const serverData = clientProps2.__serverData ?? {};
|
|
1445
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;
|
|
@@ -1554,15 +1554,18 @@ function createLambdaHandler(options, bundled) {
|
|
|
1554
1554
|
getInitProps,
|
|
1555
1555
|
getFinalProps
|
|
1556
1556
|
} = await getSsrModule();
|
|
1557
|
+
const isDataOnly = request.headers.get("Accept") === "application/json";
|
|
1557
1558
|
const { head, status, getAppBody, finalize } = await getReactResponse(request, {
|
|
1558
1559
|
document: {
|
|
1559
1560
|
body: Component,
|
|
1560
1561
|
lang: "en",
|
|
1561
1562
|
getInitProps,
|
|
1562
1563
|
getFinalProps
|
|
1563
|
-
}
|
|
1564
|
+
},
|
|
1565
|
+
singlePass: !isDataOnly,
|
|
1566
|
+
dataOnly: isDataOnly
|
|
1564
1567
|
});
|
|
1565
|
-
if (
|
|
1568
|
+
if (isDataOnly) {
|
|
1566
1569
|
const { clientProps: clientProps2 } = await finalize();
|
|
1567
1570
|
const serverData = clientProps2.__serverData ?? {};
|
|
1568
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
|
});
|