hadars 0.1.36 → 0.1.37

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/cli-lib.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { mkdir, writeFile, unlink } from 'node:fs/promises'
3
3
  import { resolve, join, dirname } from 'node:path'
4
- import { tmpdir } from 'node:os'
4
+ import { fileURLToPath } from 'node:url'
5
5
  import * as Hadars from './src/build'
6
6
  import type { HadarsOptions } from './src/types/hadars'
7
7
 
@@ -70,11 +70,16 @@ async function bundleLambda(
70
70
 
71
71
  // 3. Write a temporary entry shim that statically imports the SSR module
72
72
  // and the HTML template so esbuild can inline both.
73
- const shimPath = join(tmpdir(), `hadars-lambda-shim-${Date.now()}.ts`)
73
+ // Write the shim inside cwd so esbuild's module resolution finds local
74
+ // node_modules when walking up from the shim's directory.
75
+ // Use the absolute path to lambda.js (sibling of the CLI in dist/) so the
76
+ // shim doesn't depend on package name resolution at all.
77
+ const lambdaModule = resolve(dirname(fileURLToPath(import.meta.url)), 'lambda.js')
78
+ const shimPath = join(cwd, `.hadars-lambda-shim-${Date.now()}.ts`)
74
79
  const shim = [
75
80
  `import * as ssrModule from ${JSON.stringify(ssrBundle)};`,
76
81
  `import outHtml from ${JSON.stringify(outHtml)};`,
77
- `import { createLambdaHandler } from 'hadars/lambda';`,
82
+ `import { createLambdaHandler } from ${JSON.stringify(lambdaModule)};`,
78
83
  `import config from ${JSON.stringify(configPath)};`,
79
84
  `export const handler = createLambdaHandler(config as any, { ssrModule: ssrModule as any, outHtml });`,
80
85
  ].join('\n') + '\n'
@@ -93,16 +98,19 @@ async function bundleLambda(
93
98
  outfile: outputFile,
94
99
  sourcemap: false,
95
100
  loader: { '.html': 'text', '.tsx': 'tsx', '.ts': 'ts' },
96
- // Node built-ins are available in Lambda mark them all external.
97
- packages: 'external',
98
- // Keep React external so there's only one instance across the bundle.
99
- external: ['react', 'react-dom', '@rspack/*'],
101
+ // @rspack/* contains native binaries and is build-time only
102
+ // it is never imported at Lambda runtime, so mark it external.
103
+ // Everything else (React, hadars runtime, etc.) is bundled in to
104
+ // produce a truly self-contained single-file deployment.
105
+ external: ['@rspack/*'],
100
106
  })
101
107
  console.log(`Lambda bundle written to ${outputFile}`)
102
108
  console.log(`\nDeploy instructions:`)
103
- console.log(` 1. Upload ${outputFile} as your Lambda function code`)
104
- console.log(` 2. Set handler to: index.handler`)
105
- console.log(` 3. Upload .hadars/static/ assets to S3 and serve them via CloudFront`)
109
+ console.log(` 1. Create a staging directory with just this file:`)
110
+ console.log(` mkdir -p lambda-deploy && cp ${outputFile} lambda-deploy/lambda.mjs`)
111
+ console.log(` 2. Upload lambda-deploy/ as your Lambda function code`)
112
+ console.log(` 3. Set handler to: lambda.handler (runtime: Node.js 20.x)`)
113
+ console.log(` 4. Upload .hadars/static/ assets to S3 and serve via CloudFront`)
106
114
  console.log(` (the Lambda handler does not serve static JS/CSS — route those to S3)`)
107
115
  } finally {
108
116
  await unlink(shimPath).catch(() => {})
@@ -5,7 +5,7 @@ import {
5
5
  SLIM_ELEMENT,
6
6
  SUSPENSE_TYPE,
7
7
  createElement
8
- } from "./chunk-OZUZS2PD.js";
8
+ } from "./chunk-OS3V4CPN.js";
9
9
 
10
10
  // src/slim-react/renderContext.ts
11
11
  var MAP_KEY = "__slimReactContextMap";
@@ -577,12 +577,12 @@ function renderHostElement(tag, props, writer, isSvg) {
577
577
  }
578
578
  writer.write(`</${tag}>`);
579
579
  }
580
- var REACT_MEMO = /* @__PURE__ */ Symbol.for("react.memo");
581
- var REACT_FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
582
- var REACT_PROVIDER = /* @__PURE__ */ Symbol.for("react.provider");
583
- var REACT_CONTEXT = /* @__PURE__ */ Symbol.for("react.context");
584
- var REACT_CONSUMER = /* @__PURE__ */ Symbol.for("react.consumer");
585
- var REACT_LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
580
+ var REACT_MEMO = Symbol.for("react.memo");
581
+ var REACT_FORWARD_REF = Symbol.for("react.forward_ref");
582
+ var REACT_PROVIDER = Symbol.for("react.provider");
583
+ var REACT_CONTEXT = Symbol.for("react.context");
584
+ var REACT_CONSUMER = Symbol.for("react.consumer");
585
+ var REACT_LAZY = Symbol.for("react.lazy");
586
586
  function renderComponent(type, props, writer, isSvg) {
587
587
  const typeOf = type?.$$typeof;
588
588
  if (typeOf === REACT_MEMO) {
@@ -1,8 +1,8 @@
1
1
  // src/slim-react/types.ts
2
- var SLIM_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
3
- var REACT19_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
4
- var FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
5
- var SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for("react.suspense");
2
+ var SLIM_ELEMENT = Symbol.for("react.element");
3
+ var REACT19_ELEMENT = Symbol.for("react.transitional.element");
4
+ var FRAGMENT_TYPE = Symbol.for("react.fragment");
5
+ var SUSPENSE_TYPE = Symbol.for("react.suspense");
6
6
 
7
7
  // src/slim-react/jsx.ts
8
8
  var Fragment = FRAGMENT_TYPE;
package/dist/cli.js CHANGED
@@ -6,8 +6,8 @@ import { spawn as spawn2 } from "node:child_process";
6
6
  // cli-lib.ts
7
7
  import { existsSync as existsSync3 } from "node:fs";
8
8
  import { mkdir, writeFile, unlink } from "node:fs/promises";
9
- import { resolve, join } from "node:path";
10
- import { tmpdir } from "node:os";
9
+ import { resolve, join, dirname } from "node:path";
10
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
11
11
 
12
12
  // src/utils/proxyHandler.tsx
13
13
  var cloneHeaders = (headers) => {
@@ -1307,6 +1307,25 @@ var buildCompilerConfig = (entry, opts, includeHotPlugin) => {
1307
1307
  }
1308
1308
  }
1309
1309
  const extraPlugins = [];
1310
+ if (!isServerBuild) {
1311
+ extraPlugins.push({
1312
+ apply(compiler) {
1313
+ compiler.hooks.compilation.tap("HadarsWorkerChunkLoading", (compilation) => {
1314
+ compilation.hooks.childCompiler.tap(
1315
+ "HadarsWorkerChunkLoading",
1316
+ (childCompiler) => {
1317
+ if (childCompiler.options?.output) {
1318
+ childCompiler.options.output.chunkLoading = "import-scripts";
1319
+ }
1320
+ if (childCompiler.options?.experiments) {
1321
+ childCompiler.options.experiments.outputModule = false;
1322
+ }
1323
+ }
1324
+ );
1325
+ });
1326
+ }
1327
+ });
1328
+ }
1310
1329
  const defineValues = { ...opts.define ?? {} };
1311
1330
  if (!isServerBuild && opts.reactMode !== void 0) {
1312
1331
  defineValues["process.env.NODE_ENV"] = JSON.stringify(opts.reactMode);
@@ -1383,7 +1402,8 @@ var buildCompilerConfig = (entry, opts, includeHotPlugin) => {
1383
1402
  },
1384
1403
  isDev && !isServerBuild && new ReactRefreshPlugin(),
1385
1404
  includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
1386
- ...extraPlugins
1405
+ ...extraPlugins,
1406
+ ...opts.plugins ?? []
1387
1407
  ],
1388
1408
  ...localConfig,
1389
1409
  // Merge base resolve (modules, tsConfig, extensions) with per-build resolve
@@ -1983,6 +2003,7 @@ var dev = async (options) => {
1983
2003
  swcPlugins: options.swcPlugins,
1984
2004
  define: options.define,
1985
2005
  moduleRules: options.moduleRules,
2006
+ plugins: options.plugins,
1986
2007
  reactMode: options.reactMode,
1987
2008
  htmlTemplate: resolvedHtmlTemplate
1988
2009
  });
@@ -2201,6 +2222,7 @@ var build = async (options) => {
2201
2222
  swcPlugins: options.swcPlugins,
2202
2223
  define: options.define,
2203
2224
  moduleRules: options.moduleRules,
2225
+ plugins: options.plugins,
2204
2226
  optimization: options.optimization,
2205
2227
  reactMode: options.reactMode,
2206
2228
  htmlTemplate: resolvedHtmlTemplate
@@ -2218,7 +2240,8 @@ var build = async (options) => {
2218
2240
  mode: "production",
2219
2241
  swcPlugins: options.swcPlugins,
2220
2242
  define: options.define,
2221
- moduleRules: options.moduleRules
2243
+ moduleRules: options.moduleRules,
2244
+ plugins: options.plugins
2222
2245
  })
2223
2246
  ]);
2224
2247
  await fs.rm(tmpFilePath);
@@ -2374,11 +2397,12 @@ async function bundleLambda(config, configPath, outputFile, cwd) {
2374
2397
  console.error(`HTML template not found: ${outHtml}`);
2375
2398
  process.exit(1);
2376
2399
  }
2377
- const shimPath = join(tmpdir(), `hadars-lambda-shim-${Date.now()}.ts`);
2400
+ const lambdaModule = resolve(dirname(fileURLToPath3(import.meta.url)), "lambda.js");
2401
+ const shimPath = join(cwd, `.hadars-lambda-shim-${Date.now()}.ts`);
2378
2402
  const shim = [
2379
2403
  `import * as ssrModule from ${JSON.stringify(ssrBundle)};`,
2380
2404
  `import outHtml from ${JSON.stringify(outHtml)};`,
2381
- `import { createLambdaHandler } from 'hadars/lambda';`,
2405
+ `import { createLambdaHandler } from ${JSON.stringify(lambdaModule)};`,
2382
2406
  `import config from ${JSON.stringify(configPath)};`,
2383
2407
  `export const handler = createLambdaHandler(config as any, { ssrModule: ssrModule as any, outHtml });`
2384
2408
  ].join("\n") + "\n";
@@ -2395,17 +2419,20 @@ async function bundleLambda(config, configPath, outputFile, cwd) {
2395
2419
  outfile: outputFile,
2396
2420
  sourcemap: false,
2397
2421
  loader: { ".html": "text", ".tsx": "tsx", ".ts": "ts" },
2398
- // Node built-ins are available in Lambda mark them all external.
2399
- packages: "external",
2400
- // Keep React external so there's only one instance across the bundle.
2401
- external: ["react", "react-dom", "@rspack/*"]
2422
+ // @rspack/* contains native binaries and is build-time only
2423
+ // it is never imported at Lambda runtime, so mark it external.
2424
+ // Everything else (React, hadars runtime, etc.) is bundled in to
2425
+ // produce a truly self-contained single-file deployment.
2426
+ external: ["@rspack/*"]
2402
2427
  });
2403
2428
  console.log(`Lambda bundle written to ${outputFile}`);
2404
2429
  console.log(`
2405
2430
  Deploy instructions:`);
2406
- console.log(` 1. Upload ${outputFile} as your Lambda function code`);
2407
- console.log(` 2. Set handler to: index.handler`);
2408
- console.log(` 3. Upload .hadars/static/ assets to S3 and serve them via CloudFront`);
2431
+ console.log(` 1. Create a staging directory with just this file:`);
2432
+ console.log(` mkdir -p lambda-deploy && cp ${outputFile} lambda-deploy/lambda.mjs`);
2433
+ console.log(` 2. Upload lambda-deploy/ as your Lambda function code`);
2434
+ console.log(` 3. Set handler to: lambda.handler (runtime: Node.js 20.x)`);
2435
+ console.log(` 4. Upload .hadars/static/ assets to S3 and serve via CloudFront`);
2409
2436
  console.log(` (the Lambda handler does not serve static JS/CSS \u2014 route those to S3)`);
2410
2437
  } finally {
2411
2438
  await unlink(shimPath).catch(() => {
@@ -137,6 +137,18 @@ interface HadarsOptions {
137
137
  * ]
138
138
  */
139
139
  moduleRules?: Record<string, any>[];
140
+ /**
141
+ * Additional rspack/webpack-compatible plugins applied to both the client
142
+ * and SSR bundles. Any object that implements the `apply(compiler)` method
143
+ * (the standard webpack/rspack plugin interface) is accepted.
144
+ *
145
+ * @example
146
+ * import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
147
+ * plugins: [new SubresourceIntegrityPlugin()]
148
+ */
149
+ plugins?: Array<{
150
+ apply(compiler: any): void;
151
+ }>;
140
152
  /**
141
153
  * SSR response cache for `run()` mode. Has no effect in `dev()` mode.
142
154
  *
@@ -137,6 +137,18 @@ interface HadarsOptions {
137
137
  * ]
138
138
  */
139
139
  moduleRules?: Record<string, any>[];
140
+ /**
141
+ * Additional rspack/webpack-compatible plugins applied to both the client
142
+ * and SSR bundles. Any object that implements the `apply(compiler)` method
143
+ * (the standard webpack/rspack plugin interface) is accepted.
144
+ *
145
+ * @example
146
+ * import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
147
+ * plugins: [new SubresourceIntegrityPlugin()]
148
+ */
149
+ plugins?: Array<{
150
+ apply(compiler: any): void;
151
+ }>;
140
152
  /**
141
153
  * SSR response cache for `run()` mode. Has no effect in `dev()` mode.
142
154
  *
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default from 'react';
3
- import { A as AppContext } from './hadars-ScRgKezP.cjs';
4
- export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetAfterRenderProps, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest } from './hadars-ScRgKezP.cjs';
3
+ import { A as AppContext } from './hadars-B3b_6sj2.cjs';
4
+ export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetAfterRenderProps, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest } from './hadars-B3b_6sj2.cjs';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
 
7
7
  /** Call this before hydrating to seed the client cache from the server's data.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default from 'react';
3
- import { A as AppContext } from './hadars-ScRgKezP.js';
4
- export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetAfterRenderProps, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest } from './hadars-ScRgKezP.js';
3
+ import { A as AppContext } from './hadars-B3b_6sj2.js';
4
+ export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetAfterRenderProps, d as HadarsGetClientProps, e as HadarsGetFinalProps, f as HadarsGetInitialProps, a as HadarsOptions, g as HadarsProps, h as HadarsRequest } from './hadars-B3b_6sj2.js';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
 
7
7
  /** Call this before hydrating to seed the client cache from the server's data.
package/dist/lambda.cjs CHANGED
@@ -193,10 +193,10 @@ async function tryServeFile(filePath) {
193
193
  }
194
194
 
195
195
  // src/slim-react/types.ts
196
- var SLIM_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
197
- var REACT19_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
198
- var FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
199
- var SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for("react.suspense");
196
+ var SLIM_ELEMENT = Symbol.for("react.element");
197
+ var REACT19_ELEMENT = Symbol.for("react.transitional.element");
198
+ var FRAGMENT_TYPE = Symbol.for("react.fragment");
199
+ var SUSPENSE_TYPE = Symbol.for("react.suspense");
200
200
 
201
201
  // src/slim-react/jsx.ts
202
202
  function createElement(type, props, ...children) {
@@ -754,12 +754,12 @@ function renderHostElement(tag, props, writer, isSvg) {
754
754
  }
755
755
  writer.write(`</${tag}>`);
756
756
  }
757
- var REACT_MEMO = /* @__PURE__ */ Symbol.for("react.memo");
758
- var REACT_FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
759
- var REACT_PROVIDER = /* @__PURE__ */ Symbol.for("react.provider");
760
- var REACT_CONTEXT = /* @__PURE__ */ Symbol.for("react.context");
761
- var REACT_CONSUMER = /* @__PURE__ */ Symbol.for("react.consumer");
762
- var REACT_LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
757
+ var REACT_MEMO = Symbol.for("react.memo");
758
+ var REACT_FORWARD_REF = Symbol.for("react.forward_ref");
759
+ var REACT_PROVIDER = Symbol.for("react.provider");
760
+ var REACT_CONTEXT = Symbol.for("react.context");
761
+ var REACT_CONSUMER = Symbol.for("react.consumer");
762
+ var REACT_LAZY = Symbol.for("react.lazy");
763
763
  function renderComponent(type, props, writer, isSvg) {
764
764
  const typeOf = type?.$$typeof;
765
765
  if (typeOf === REACT_MEMO) {
@@ -1161,22 +1161,10 @@ var getReactResponse = async (req, opts) => {
1161
1161
  var HEAD_MARKER = '<meta name="HADARS_HEAD">';
1162
1162
  var BODY_MARKER = '<meta name="HADARS_BODY">';
1163
1163
  var encoder = new TextEncoder();
1164
- async function buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml) {
1165
- const responseStream = new ReadableStream({
1166
- async start(controller) {
1167
- const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
1168
- controller.enqueue(encoder.encode(precontentHtml));
1169
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
1170
- controller.enqueue(encoder.encode(
1171
- `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent
1172
- ));
1173
- controller.close();
1174
- }
1175
- });
1176
- return new Response(responseStream, {
1177
- headers: { "Content-Type": "text/html; charset=utf-8" },
1178
- status
1179
- });
1164
+ async function buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml) {
1165
+ const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
1166
+ const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
1167
+ return precontentHtml + `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent;
1180
1168
  }
1181
1169
  var makePrecontentHtmlGetter = (htmlFilePromise) => {
1182
1170
  let preHead = null;
@@ -1340,6 +1328,14 @@ function createLambdaHandler(options, bundled) {
1340
1328
  const fetchHandler = options.fetch;
1341
1329
  const handleProxy = createProxyHandler(options);
1342
1330
  const getPrecontentHtml = bundled ? makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml)) : makePrecontentHtmlGetter(import_promises2.default.readFile(import_node_path.default.join(cwd, StaticPath, "out.html"), "utf-8"));
1331
+ let ssrModulePromise = null;
1332
+ const getSsrModule = () => {
1333
+ if (bundled) return Promise.resolve(bundled.ssrModule);
1334
+ if (!ssrModulePromise) {
1335
+ ssrModulePromise = import((0, import_node_url.pathToFileURL)(import_node_path.default.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
1336
+ }
1337
+ return ssrModulePromise;
1338
+ };
1343
1339
  const runHandler = async (req) => {
1344
1340
  const request = parseRequest(req);
1345
1341
  if (fetchHandler) {
@@ -1369,7 +1365,7 @@ function createLambdaHandler(options, bundled) {
1369
1365
  getInitProps,
1370
1366
  getAfterRenderProps,
1371
1367
  getFinalProps
1372
- } = bundled ? bundled.ssrModule : await import((0, import_node_url.pathToFileURL)(import_node_path.default.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
1368
+ } = await getSsrModule();
1373
1369
  const { bodyHtml, clientProps, status, headHtml } = await getReactResponse(request, {
1374
1370
  document: {
1375
1371
  body: Component,
@@ -1386,7 +1382,11 @@ function createLambdaHandler(options, bundled) {
1386
1382
  headers: { "Content-Type": "application/json; charset=utf-8" }
1387
1383
  });
1388
1384
  }
1389
- return buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml);
1385
+ const html = await buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml);
1386
+ return new Response(html, {
1387
+ status,
1388
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1389
+ });
1390
1390
  } catch (err) {
1391
1391
  console.error("[hadars] SSR render error:", err);
1392
1392
  return new Response("Internal Server Error", { status: 500 });
package/dist/lambda.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-ScRgKezP.cjs';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-B3b_6sj2.cjs';
2
2
  import 'react';
3
3
 
4
4
  /**
package/dist/lambda.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-ScRgKezP.js';
1
+ import { H as HadarsEntryModule, a as HadarsOptions } from './hadars-B3b_6sj2.js';
2
2
  import 'react';
3
3
 
4
4
  /**
package/dist/lambda.js CHANGED
@@ -3,16 +3,16 @@ import {
3
3
  } from "./chunk-UNQSQIOO.js";
4
4
  import {
5
5
  renderToString
6
- } from "./chunk-OID7K4D3.js";
6
+ } from "./chunk-F7IIMM3J.js";
7
7
  import {
8
8
  createElement
9
- } from "./chunk-OZUZS2PD.js";
9
+ } from "./chunk-OS3V4CPN.js";
10
10
 
11
11
  // src/lambda.ts
12
12
  import "react";
13
- import pathMod from "path";
14
- import { pathToFileURL } from "url";
15
- import fs from "fs/promises";
13
+ import pathMod from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ import fs from "node:fs/promises";
16
16
 
17
17
  // src/utils/proxyHandler.tsx
18
18
  var cloneHeaders = (headers) => {
@@ -127,7 +127,7 @@ var parseRequest = (request) => {
127
127
  };
128
128
 
129
129
  // src/utils/staticFile.ts
130
- import { readFile, stat } from "fs/promises";
130
+ import { readFile, stat } from "node:fs/promises";
131
131
  var MIME = {
132
132
  html: "text/html; charset=utf-8",
133
133
  htm: "text/html; charset=utf-8",
@@ -256,22 +256,10 @@ var getReactResponse = async (req, opts) => {
256
256
  var HEAD_MARKER = '<meta name="HADARS_HEAD">';
257
257
  var BODY_MARKER = '<meta name="HADARS_BODY">';
258
258
  var encoder = new TextEncoder();
259
- async function buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml) {
260
- const responseStream = new ReadableStream({
261
- async start(controller) {
262
- const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
263
- controller.enqueue(encoder.encode(precontentHtml));
264
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
265
- controller.enqueue(encoder.encode(
266
- `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent
267
- ));
268
- controller.close();
269
- }
270
- });
271
- return new Response(responseStream, {
272
- headers: { "Content-Type": "text/html; charset=utf-8" },
273
- status
274
- });
259
+ async function buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml) {
260
+ const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
261
+ const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
262
+ return precontentHtml + `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent;
275
263
  }
276
264
  var makePrecontentHtmlGetter = (htmlFilePromise) => {
277
265
  let preHead = null;
@@ -435,6 +423,14 @@ function createLambdaHandler(options, bundled) {
435
423
  const fetchHandler = options.fetch;
436
424
  const handleProxy = createProxyHandler(options);
437
425
  const getPrecontentHtml = bundled ? makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml)) : makePrecontentHtmlGetter(fs.readFile(pathMod.join(cwd, StaticPath, "out.html"), "utf-8"));
426
+ let ssrModulePromise = null;
427
+ const getSsrModule = () => {
428
+ if (bundled) return Promise.resolve(bundled.ssrModule);
429
+ if (!ssrModulePromise) {
430
+ ssrModulePromise = import(pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
431
+ }
432
+ return ssrModulePromise;
433
+ };
438
434
  const runHandler = async (req) => {
439
435
  const request = parseRequest(req);
440
436
  if (fetchHandler) {
@@ -464,7 +460,7 @@ function createLambdaHandler(options, bundled) {
464
460
  getInitProps,
465
461
  getAfterRenderProps,
466
462
  getFinalProps
467
- } = bundled ? bundled.ssrModule : await import(pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href);
463
+ } = await getSsrModule();
468
464
  const { bodyHtml, clientProps, status, headHtml } = await getReactResponse(request, {
469
465
  document: {
470
466
  body: Component,
@@ -481,7 +477,11 @@ function createLambdaHandler(options, bundled) {
481
477
  headers: { "Content-Type": "application/json; charset=utf-8" }
482
478
  });
483
479
  }
484
- return buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml);
480
+ const html = await buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml);
481
+ return new Response(html, {
482
+ status,
483
+ headers: { "Content-Type": "text/html; charset=utf-8" }
484
+ });
485
485
  } catch (err) {
486
486
  console.error("[hadars] SSR render error:", err);
487
487
  return new Response("Internal Server Error", { status: 500 });
@@ -77,10 +77,10 @@ __export(slim_react_exports, {
77
77
  module.exports = __toCommonJS(slim_react_exports);
78
78
 
79
79
  // src/slim-react/types.ts
80
- var SLIM_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
81
- var REACT19_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
82
- var FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
83
- var SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for("react.suspense");
80
+ var SLIM_ELEMENT = Symbol.for("react.element");
81
+ var REACT19_ELEMENT = Symbol.for("react.transitional.element");
82
+ var FRAGMENT_TYPE = Symbol.for("react.fragment");
83
+ var SUSPENSE_TYPE = Symbol.for("react.suspense");
84
84
 
85
85
  // src/slim-react/jsx.ts
86
86
  var Fragment = FRAGMENT_TYPE;
@@ -679,12 +679,12 @@ function renderHostElement(tag, props, writer, isSvg) {
679
679
  }
680
680
  writer.write(`</${tag}>`);
681
681
  }
682
- var REACT_MEMO = /* @__PURE__ */ Symbol.for("react.memo");
683
- var REACT_FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
684
- var REACT_PROVIDER = /* @__PURE__ */ Symbol.for("react.provider");
685
- var REACT_CONTEXT = /* @__PURE__ */ Symbol.for("react.context");
686
- var REACT_CONSUMER = /* @__PURE__ */ Symbol.for("react.consumer");
687
- var REACT_LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
682
+ var REACT_MEMO = Symbol.for("react.memo");
683
+ var REACT_FORWARD_REF = Symbol.for("react.forward_ref");
684
+ var REACT_PROVIDER = Symbol.for("react.provider");
685
+ var REACT_CONTEXT = Symbol.for("react.context");
686
+ var REACT_CONSUMER = Symbol.for("react.consumer");
687
+ var REACT_LAZY = Symbol.for("react.lazy");
688
688
  function renderComponent(type, props, writer, isSvg) {
689
689
  const typeOf = type?.$$typeof;
690
690
  if (typeOf === REACT_MEMO) {
@@ -33,7 +33,7 @@ import {
33
33
  useSyncExternalStore,
34
34
  useTransition,
35
35
  version
36
- } from "../chunk-OID7K4D3.js";
36
+ } from "../chunk-F7IIMM3J.js";
37
37
  import {
38
38
  FRAGMENT_TYPE,
39
39
  Fragment,
@@ -41,7 +41,7 @@ import {
41
41
  SUSPENSE_TYPE,
42
42
  createElement,
43
43
  jsx
44
- } from "../chunk-OZUZS2PD.js";
44
+ } from "../chunk-OS3V4CPN.js";
45
45
  export {
46
46
  Children,
47
47
  Component,
@@ -28,8 +28,10 @@ __export(jsx_runtime_exports, {
28
28
  module.exports = __toCommonJS(jsx_runtime_exports);
29
29
 
30
30
  // src/slim-react/types.ts
31
- var SLIM_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
32
- var FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
31
+ var SLIM_ELEMENT = Symbol.for("react.element");
32
+ var REACT19_ELEMENT = Symbol.for("react.transitional.element");
33
+ var FRAGMENT_TYPE = Symbol.for("react.fragment");
34
+ var SUSPENSE_TYPE = Symbol.for("react.suspense");
33
35
 
34
36
  // src/slim-react/jsx.ts
35
37
  var Fragment = FRAGMENT_TYPE;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  jsx
4
- } from "../chunk-OZUZS2PD.js";
4
+ } from "../chunk-OS3V4CPN.js";
5
5
  export {
6
6
  Fragment,
7
7
  jsx,
package/dist/ssr-watch.js CHANGED
@@ -208,6 +208,25 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
208
208
  }
209
209
  }
210
210
  const extraPlugins = [];
211
+ if (!isServerBuild) {
212
+ extraPlugins.push({
213
+ apply(compiler) {
214
+ compiler.hooks.compilation.tap("HadarsWorkerChunkLoading", (compilation) => {
215
+ compilation.hooks.childCompiler.tap(
216
+ "HadarsWorkerChunkLoading",
217
+ (childCompiler) => {
218
+ if (childCompiler.options?.output) {
219
+ childCompiler.options.output.chunkLoading = "import-scripts";
220
+ }
221
+ if (childCompiler.options?.experiments) {
222
+ childCompiler.options.experiments.outputModule = false;
223
+ }
224
+ }
225
+ );
226
+ });
227
+ }
228
+ });
229
+ }
211
230
  const defineValues = { ...opts.define ?? {} };
212
231
  if (!isServerBuild && opts.reactMode !== void 0) {
213
232
  defineValues["process.env.NODE_ENV"] = JSON.stringify(opts.reactMode);
@@ -284,7 +303,8 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
284
303
  },
285
304
  isDev && !isServerBuild && new ReactRefreshPlugin(),
286
305
  includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
287
- ...extraPlugins
306
+ ...extraPlugins,
307
+ ...opts.plugins ?? []
288
308
  ],
289
309
  ...localConfig,
290
310
  // Merge base resolve (modules, tsConfig, extensions) with per-build resolve
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -79,7 +79,7 @@
79
79
  "@types/react-dom": "^19.2.3",
80
80
  "react": "^19.2.4",
81
81
  "react-dom": "^19.2.4",
82
- "tsup": "^8.5.1",
82
+ "tsup": "^8.3.5",
83
83
  "typescript": "^5.9.3"
84
84
  },
85
85
  "dependencies": {
package/src/build.ts CHANGED
@@ -341,6 +341,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
341
341
  swcPlugins: options.swcPlugins,
342
342
  define: options.define,
343
343
  moduleRules: options.moduleRules,
344
+ plugins: options.plugins,
344
345
  reactMode: options.reactMode,
345
346
  htmlTemplate: resolvedHtmlTemplate,
346
347
  });
@@ -596,6 +597,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
596
597
  swcPlugins: options.swcPlugins,
597
598
  define: options.define,
598
599
  moduleRules: options.moduleRules,
600
+ plugins: options.plugins,
599
601
  optimization: options.optimization,
600
602
  reactMode: options.reactMode,
601
603
  htmlTemplate: resolvedHtmlTemplate,
@@ -614,6 +616,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
614
616
  swcPlugins: options.swcPlugins,
615
617
  define: options.define,
616
618
  moduleRules: options.moduleRules,
619
+ plugins: options.plugins,
617
620
  }),
618
621
  ]);
619
622
  await fs.rm(tmpFilePath);
package/src/lambda.ts CHANGED
@@ -24,7 +24,7 @@ import { createProxyHandler } from './utils/proxyHandler';
24
24
  import { parseRequest } from './utils/request';
25
25
  import { tryServeFile } from './utils/staticFile';
26
26
  import { getReactResponse } from './utils/response';
27
- import { buildSsrResponse, makePrecontentHtmlGetter, createRenderCache } from './utils/ssrHandler';
27
+ import { buildSsrResponse, buildSsrHtml, makePrecontentHtmlGetter, createRenderCache } from './utils/ssrHandler';
28
28
  import type { HadarsOptions, HadarsEntryModule, HadarsProps } from './types/hadars';
29
29
 
30
30
  // ── Lambda event / response types ────────────────────────────────────────────
@@ -189,6 +189,21 @@ export function createLambdaHandler(options: HadarsOptions, bundled?: LambdaBund
189
189
  ? makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml))
190
190
  : makePrecontentHtmlGetter(fs.readFile(pathMod.join(cwd, StaticPath, 'out.html'), 'utf-8'));
191
191
 
192
+ // Hoist the SSR module reference so it is resolved once, not on every
193
+ // request. In bundled mode the module is already in-memory; in file-based
194
+ // mode we lazily import it and cache the promise so Node's module cache is
195
+ // only consulted once.
196
+ let ssrModulePromise: Promise<HadarsEntryModule<any>> | null = null;
197
+ const getSsrModule = (): Promise<HadarsEntryModule<any>> => {
198
+ if (bundled) return Promise.resolve(bundled.ssrModule);
199
+ if (!ssrModulePromise) {
200
+ ssrModulePromise = import(
201
+ pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href
202
+ ) as Promise<HadarsEntryModule<any>>;
203
+ }
204
+ return ssrModulePromise;
205
+ };
206
+
192
207
  const runHandler = async (req: Request): Promise<Response> => {
193
208
  const request = parseRequest(req);
194
209
 
@@ -227,9 +242,7 @@ export function createLambdaHandler(options: HadarsOptions, bundled?: LambdaBund
227
242
  getInitProps,
228
243
  getAfterRenderProps,
229
244
  getFinalProps,
230
- } = bundled
231
- ? bundled.ssrModule
232
- : (await import(pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href)) as HadarsEntryModule<any>;
245
+ } = await getSsrModule();
233
246
 
234
247
  const { bodyHtml, clientProps, status, headHtml } = await getReactResponse(request, {
235
248
  document: {
@@ -249,7 +262,13 @@ export function createLambdaHandler(options: HadarsOptions, bundled?: LambdaBund
249
262
  });
250
263
  }
251
264
 
252
- return buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml);
265
+ // Build the HTML string directly — avoids creating a ReadableStream
266
+ // that would immediately be drained by the Lambda response serialiser.
267
+ const html = await buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml);
268
+ return new Response(html, {
269
+ status,
270
+ headers: { 'Content-Type': 'text/html; charset=utf-8' },
271
+ });
253
272
  } catch (err: any) {
254
273
  console.error('[hadars] SSR render error:', err);
255
274
  return new Response('Internal Server Error', { status: 500 });
@@ -142,6 +142,16 @@ export interface HadarsOptions {
142
142
  * ]
143
143
  */
144
144
  moduleRules?: Record<string, any>[];
145
+ /**
146
+ * Additional rspack/webpack-compatible plugins applied to both the client
147
+ * and SSR bundles. Any object that implements the `apply(compiler)` method
148
+ * (the standard webpack/rspack plugin interface) is accepted.
149
+ *
150
+ * @example
151
+ * import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
152
+ * plugins: [new SubresourceIntegrityPlugin()]
153
+ */
154
+ plugins?: Array<{ apply(compiler: any): void }>;
145
155
  /**
146
156
  * SSR response cache for `run()` mode. Has no effect in `dev()` mode.
147
157
  *
@@ -136,6 +136,8 @@ interface EntryOptions {
136
136
  optimization?: Record<string, unknown>;
137
137
  // additional module rules appended after the built-in rules
138
138
  moduleRules?: Record<string, any>[];
139
+ // additional rspack/webpack-compatible plugins (applied after built-in plugins)
140
+ plugins?: Array<{ apply(compiler: any): void }>;
139
141
  // force React runtime mode independently of build mode (client only)
140
142
  reactMode?: 'development' | 'production';
141
143
  }
@@ -264,6 +266,30 @@ const buildCompilerConfig = (
264
266
  }
265
267
 
266
268
  const extraPlugins: any[] = [];
269
+
270
+ // Built-in plugin: force classic chunk loading for web worker sub-compilations.
271
+ // Without this, worker child compilers inherit the parent's outputModule:true context
272
+ // and may emit ES module chunks that cannot be loaded inside classic workers
273
+ // via importScripts. Applied to client builds only — SSR doesn't spawn workers.
274
+ if (!isServerBuild) {
275
+ extraPlugins.push({
276
+ apply(compiler: any) {
277
+ compiler.hooks.compilation.tap('HadarsWorkerChunkLoading', (compilation: any) => {
278
+ compilation.hooks.childCompiler.tap(
279
+ 'HadarsWorkerChunkLoading',
280
+ (childCompiler: any) => {
281
+ if (childCompiler.options?.output) {
282
+ childCompiler.options.output.chunkLoading = 'import-scripts';
283
+ }
284
+ if (childCompiler.options?.experiments) {
285
+ childCompiler.options.experiments.outputModule = false;
286
+ }
287
+ },
288
+ );
289
+ });
290
+ },
291
+ });
292
+ }
267
293
  const defineValues: Record<string, string> = { ...(opts.define ?? {}) };
268
294
  // When reactMode overrides the React runtime we must also set process.env.NODE_ENV
269
295
  // so React picks its dev/prod bundle, independently of the rspack build mode.
@@ -352,6 +378,7 @@ const buildCompilerConfig = (
352
378
  isDev && !isServerBuild && new ReactRefreshPlugin(),
353
379
  includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
354
380
  ...extraPlugins,
381
+ ...(opts.plugins ?? []),
355
382
  ],
356
383
  ...localConfig,
357
384
  // Merge base resolve (modules, tsConfig, extensions) with per-build resolve
@@ -36,6 +36,27 @@ export async function buildSsrResponse(
36
36
  });
37
37
  }
38
38
 
39
+ /**
40
+ * Like {@link buildSsrResponse} but returns the complete HTML string directly
41
+ * instead of wrapping it in a streaming Response. Use this in environments
42
+ * where streaming is not beneficial (e.g. AWS Lambda) to avoid the
43
+ * ReadableStream allocation and the subsequent `.text()` drain overhead.
44
+ */
45
+ export async function buildSsrHtml(
46
+ bodyHtml: string,
47
+ clientProps: Record<string, unknown>,
48
+ headHtml: string,
49
+ getPrecontentHtml: (headHtml: string) => Promise<[string, string]>,
50
+ ): Promise<string> {
51
+ const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
52
+ const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
53
+ return (
54
+ precontentHtml +
55
+ `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` +
56
+ postContent
57
+ );
58
+ }
59
+
39
60
  /**
40
61
  * Returns a function that parses `out.html` into pre-head, post-head, and
41
62
  * post-content segments and caches the result. Call the returned function with