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.
@@ -137,8 +137,8 @@ var ATTR = {
137
137
  fetchPriority: "fetchpriority",
138
138
  hrefLang: "hreflang"
139
139
  };
140
- function renderHeadTag(tag, id, opts, selfClose = false) {
141
- let attrs = ` id="${escAttr(id)}"`;
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 existsSync3 } from "node:fs";
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, id, opts, selfClose = false) {
1186
- let attrs = ` id="${escAttr(id)}"`;
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 fs from "node:fs/promises";
1790
- import { existsSync as existsSync2 } from "node:fs";
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
- const list = this.byType.get(node.internal.type) ?? [];
1961
- const idx = list.findIndex((n) => n.id === node.id);
1962
- if (idx >= 0)
1963
- list[idx] = node;
1964
- else
1965
- list.push(node);
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
- return this.byType.get(type) ?? [];
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 fs.readFile(templatePath, "utf-8");
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 fs.access(cachedPath);
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 fs.writeFile(cachedPath, processedHtml);
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
- p.reject(new Error(error));
2437
- else
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 = () => fs.mkdir(HADARS_TMP_DIR, { recursive: true });
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 fs.readFile(pkgPath, "utf-8"));
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 && existsSync2(tsPath)) {
2651
+ if (isBun && existsSync3(tsPath)) {
2556
2652
  return ["bun", tsPath];
2557
2653
  }
2558
- if (isDeno && existsSync2(tsPath)) {
2654
+ if (isDeno && existsSync3(tsPath)) {
2559
2655
  return ["deno", "run", "--allow-all", tsPath];
2560
2656
  }
2561
- if (existsSync2(tsPath)) {
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 (existsSync2(jsPath)) {
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 fs.rm(HadarsFolder, { recursive: true, force: true });
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 fs.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
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 fs.writeFile(tmpFilePath, clientScript);
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(() => fs.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8")));
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 path2 = url.pathname;
2788
- const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path2));
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, path2));
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 fs.readFile(clientScriptPath2, "utf-8")).replace("$_MOD_PATH$", entry + getSuffix(options.mode));
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 fs.readFile(srcClientPath, "utf-8")).replace("$_MOD_PATH$", entry + `?v=${Date.now()}`);
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 fs.writeFile(tmpFilePath, clientScript);
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 fs.rm(tmpFilePath);
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 = existsSync2(workerJs) ? workerJs : workerTs;
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(fs.readFile(pathMod2.join(__dirname3, StaticPath, "out.html"), "utf-8"));
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 path2 = url.pathname;
2943
- const staticRes = await tryServeFileCached(pathMod2.join(__dirname3, StaticPath, path2));
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, path2));
3058
+ const projectRes = await tryServeFileCached(pathMod2.join(projectStaticPath, path3));
2947
3059
  if (projectRes)
2948
3060
  return projectRes;
2949
- const routeClean = path2.replace(/(^\/|\/$)/g, "");
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 (existsSync3(p))
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 (!existsSync3(ssrBundle)) {
3260
+ if (!existsSync4(ssrBundle)) {
3143
3261
  console.error(`SSR bundle not found: ${ssrBundle}`);
3144
3262
  process.exit(1);
3145
3263
  }
3146
- if (!existsSync3(outHtml)) {
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: path2, error } of errors)
3188
- console.error(` [ERR] ${path2}: ${error.message}`);
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 (!existsSync3(ssrBundle)) {
3320
+ if (!existsSync4(ssrBundle)) {
3203
3321
  console.error(`SSR bundle not found: ${ssrBundle}`);
3204
3322
  process.exit(1);
3205
3323
  }
3206
- if (!existsSync3(outHtml)) {
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 (!existsSync3(ssrBundle)) {
3377
+ if (!existsSync4(ssrBundle)) {
3260
3378
  console.error(`SSR bundle not found: ${ssrBundle}`);
3261
3379
  process.exit(1);
3262
3380
  }
3263
- if (!existsSync3(outHtml)) {
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 (existsSync3(dir)) {
3787
+ if (existsSync4(dir)) {
3670
3788
  console.error(`Directory already exists: ${dir}`);
3671
3789
  process.exit(1);
3672
3790
  }
@@ -1188,8 +1188,8 @@ var ATTR = {
1188
1188
  fetchPriority: "fetchpriority",
1189
1189
  hrefLang: "hreflang"
1190
1190
  };
1191
- function renderHeadTag(tag, id, opts, selfClose = false) {
1192
- let attrs = ` id="${escAttr(id)}"`;
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 (request.headers.get("Accept") === "application/json") {
1445
+ if (isDataOnly) {
1443
1446
  const { clientProps: clientProps2 } = await finalize();
1444
1447
  const serverData = clientProps2.__serverData ?? {};
1445
1448
  return new Response(JSON.stringify({ serverData }), {
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.cjs';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-gkIw8CFo.cjs';
2
2
 
3
3
  /**
4
4
  * Cloudflare Workers adapter for hadars.
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.js';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-gkIw8CFo.js';
2
2
 
3
3
  /**
4
4
  * Cloudflare Workers adapter for hadars.
@@ -6,7 +6,7 @@ import {
6
6
  getReactResponse,
7
7
  makePrecontentHtmlGetter,
8
8
  parseRequest
9
- } from "./chunk-B4NE4AIT.js";
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 (request.headers.get("Accept") === "application/json") {
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-CPplIz_z.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-CPplIz_z.cjs';
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-CPplIz_z.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-CPplIz_z.js';
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, id, opts, selfClose = false) {
1232
- let attrs = ` id="${escAttr(id)}"`;
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 (request.headers.get("Accept") === "application/json") {
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
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.cjs';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-gkIw8CFo.cjs';
2
2
 
3
3
  /**
4
4
  * AWS Lambda adapter for hadars.
package/dist/lambda.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-CPplIz_z.js';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-gkIw8CFo.js';
2
2
 
3
3
  /**
4
4
  * AWS Lambda adapter for hadars.
package/dist/lambda.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getReactResponse,
7
7
  makePrecontentHtmlGetter,
8
8
  parseRequest
9
- } from "./chunk-B4NE4AIT.js";
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 (request.headers.get("Accept") === "application/json") {
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, id, opts, selfClose = false) {
1030
- let attrs = ` id="${escAttr(id)}"`;
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
- parentPort.on("message", async (msg) => {
1118
- const { id, type, request } = msg;
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
- await init();
1121
- if (type !== "renderFull")
1122
- return;
1123
- const { html, headHtml, status } = await runFullLifecycle(request);
1124
- parentPort.postMessage({ id, html, headHtml, status });
1125
- } catch (err) {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",