@bananacool467/authtics-host 0.2.5 → 0.2.7

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/index.d.ts CHANGED
@@ -27,4 +27,5 @@ declare module "http" {
27
27
  import { defineConfig } from './config.js';
28
28
  import App from './cli.js';
29
29
  import { Route, RouteMap, Paused } from './route.js';
30
- export { App, Route, RouteMap, Paused, defineConfig };
30
+ import { load as loadBAPI, Cookies } from "./BrowserAPIs.js";
31
+ export { App, Route, RouteMap, Paused, defineConfig, loadBAPI, Cookies };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defineConfig } from './config.js';
2
2
  import App from './cli.js';
3
3
  import { Route, RouteMap, Paused } from './route.js';
4
- export { App, Route, RouteMap, Paused, defineConfig };
4
+ import { load as loadBAPI, Cookies } from "./BrowserAPIs.js";
5
+ export { App, Route, RouteMap, Paused, defineConfig, loadBAPI, Cookies };
@@ -1,3 +1,7 @@
1
+ export declare function deleteSrtwsServer(): void;
2
+ export declare function handleSrtwsConnection(server: any, tsxPath: string, config: any, reqUrl?: string, reqDAT?: string, rmvFileHandler?: (p: string) => void, fileHandler?: (f: string, p: string) => void): boolean;
1
3
  export type CompilerType = 'esbuild';
4
+ export type RenderMode = 'ssr' | 'csr' | 'ssg' | 'srtws';
2
5
  export declare function findFileUpwards(startDir: string, relativePath: string): string | null;
3
- export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean, autoPageDir?: string, appDir?: string, fileHandler?: (f: string, p: string) => void, rmvFileHandler?: (p: string) => void, reqDAT?: string | undefined, configDAT?: string | string[] | undefined): Promise<string>;
6
+ export declare function handleHTML(rawHtml: string, actuallyShowPanel: boolean, configDAT?: string | string[], userDAT?: string, hotReload?: boolean): Promise<string>;
7
+ export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean, autoPageDir?: string, appDir?: string, fileHandler?: (f: string, p: string) => void, rmvFileHandler?: (p: string) => void, reqDAT?: string | undefined, configDAT?: string | string[] | undefined, renderMode?: RenderMode, patchForServer?: boolean, prebuildOnly?: boolean): Promise<string>;
@@ -1,12 +1,45 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import * as esbuild from "esbuild";
5
5
  import React from "react";
6
6
  import { renderToString } from "react-dom/server";
7
- import { tempFileForTsx, writeTempFile, readTempFile, ensureTempDirs, } from "./rendererTempFiles.js";
7
+ import { tempFileForTsx, clientBundleForTsx, serverBundleForTsx, ssgHtmlForTsx, getBuildStyle, getBuildTrigger, writeTempFile, readTempFile, ensureTempDirs, } from "./rendererTempFiles.js";
8
8
  import { createRequire } from "node:module";
9
9
  const customRequire = createRequire(import.meta.url);
10
+ import { WebSocketServer } from "ws";
11
+ let srtwsServer = null;
12
+ export function deleteSrtwsServer() {
13
+ if (srtwsServer)
14
+ srtwsServer.close();
15
+ srtwsServer = null;
16
+ }
17
+ export function handleSrtwsConnection(server, tsxPath, config, reqUrl, reqDAT, rmvFileHandler, fileHandler) {
18
+ const wss = new WebSocketServer({ noServer: true });
19
+ srtwsServer = wss;
20
+ server.on("upgrade", (req, socket, head) => {
21
+ if (req.url === "/__authtics_srtws__") {
22
+ wss.handleUpgrade(req, socket, head, (ws) => {
23
+ wss.emit("connection", ws, req);
24
+ });
25
+ }
26
+ });
27
+ wss.on("connection", (ws) => {
28
+ const renderAndSend = async () => {
29
+ const pageHtml = await renderPage(tsxPath, reqUrl, config.devMode, config.compilerType, config.hotReload, config.autoPage, config.autoPageDir, config.appDir, fileHandler, rmvFileHandler, reqDAT, config.dat, 'csr', config.patchForSsr);
30
+ ws.send(JSON.stringify({ type: "DOM", html: pageHtml }));
31
+ };
32
+ ws.on("message", () => {
33
+ try {
34
+ renderAndSend();
35
+ }
36
+ catch (err) {
37
+ console.error("[HostJS] SRTWS error:", err);
38
+ }
39
+ });
40
+ });
41
+ return true;
42
+ }
10
43
  const __filename = fileURLToPath(import.meta.url);
11
44
  const __dirname = path.dirname(__filename);
12
45
  const NODE_BUILTINS = [
@@ -23,6 +56,34 @@ const NODE_BUILTINS = [
23
56
  'node:module', 'node:process', 'node:string_decoder', 'node:querystring',
24
57
  'express', 'jiti',
25
58
  ];
59
+ const SRTWS_CLIENT_SCRIPT = `<script>
60
+ (function() {
61
+ const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
62
+ const ws = new WebSocket(protocol + window.location.host + '/__authtics_srtws__');
63
+
64
+ ws.onmessage = function(event) {
65
+ const msg = JSON.parse(event.data);
66
+ if (msg.type === 'DOM') {
67
+ document.documentElement.innerHTML = msg.html;
68
+ const scripts = document.documentElement.querySelectorAll('script');
69
+
70
+ scripts.forEach(oldScript => {
71
+ const newScript = document.createElement('script');
72
+ Array.from(oldScript.attributes).forEach(attr => {
73
+ newScript.setAttribute(attr.name, attr.value);
74
+ });
75
+ newScript.textContent = oldScript.textContent;
76
+ oldScript.parentNode.replaceChild(newScript, oldScript);
77
+ });
78
+ }
79
+ };
80
+
81
+
82
+ ws.onopen = function() {
83
+ ws.send(JSON.stringify({}));
84
+ };
85
+ })();
86
+ </script>`;
26
87
  const nodeStubPlugin = {
27
88
  name: 'browser-node-stub',
28
89
  setup(build) {
@@ -34,7 +95,6 @@ const nodeStubPlugin = {
34
95
  }));
35
96
  build.onLoad({ filter: /.*/, namespace: 'browser-node-stub' }, () => ({
36
97
  contents: `
37
- // Node.js built-in stub for browser builds
38
98
  const stub = new Proxy({}, { get: () => () => undefined });
39
99
  export default stub;
40
100
  `,
@@ -42,9 +102,11 @@ const nodeStubPlugin = {
42
102
  }));
43
103
  },
44
104
  };
45
- async function buildWithEsbuild(virtualContent, resolveDir, platform) {
105
+ async function buildWithEsbuild(virtualContent, resolveDir, platform, patchForServer = false, options = {}) {
46
106
  const toFwdSlash = (p) => p.split(path.sep).join('/');
47
107
  const isNode = platform === 'node';
108
+ const sourceMaps = options.sourceMaps === true;
109
+ const outfile = path.resolve(options.outfile ?? path.join(resolveDir, `virtual-entry-${platform}.js`));
48
110
  const result = await esbuild.build({
49
111
  stdin: {
50
112
  contents: virtualContent,
@@ -53,13 +115,91 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
53
115
  },
54
116
  bundle: true,
55
117
  write: false,
118
+ outfile,
56
119
  format: isNode ? 'cjs' : 'iife',
57
120
  platform,
58
121
  jsx: 'automatic',
59
- sourcemap: isNode ? false : false,
60
122
  target: isNode ? ['node18'] : ['es2020'],
61
- minify: true,
123
+ sourcemap: sourceMaps ? "external" : false,
124
+ sourcesContent: sourceMaps,
125
+ minify: options.minify ?? !sourceMaps,
62
126
  treeShaking: true,
127
+ banner: (isNode && patchForServer) ? {
128
+ js: `if (typeof globalThis.window === "undefined") {
129
+ const noop = () => {};
130
+
131
+ const createStorageMock = () => {
132
+ let store = {};
133
+ return {
134
+ getItem: (key) => store[key] ?? null,
135
+ setItem: (key, val) => { store[key] = String(val); },
136
+ removeItem: (key) => { delete store[key]; },
137
+ clear: () => { store = {}; },
138
+ key: (i) => Object.keys(store)[i] ?? null,
139
+ get length() { return Object.keys(store).length; },
140
+ };
141
+ };
142
+
143
+ const mockLocalStorage = createStorageMock();
144
+ const mockSessionStorage = createStorageMock();
145
+
146
+ const mockElement = {
147
+ setAttribute: noop,
148
+ getAttribute: () => null,
149
+ removeAttribute: noop,
150
+ style: {},
151
+ appendChild: (node) => node,
152
+ removeChild: (node) => node,
153
+ addEventListener: noop,
154
+ removeEventListener: noop,
155
+ classList: { add: noop, remove: noop, toggle: noop, contains: () => false },
156
+ };
157
+
158
+ const mockDocument = {
159
+ createElement: () => mockElement,
160
+ querySelector: () => null,
161
+ querySelectorAll: () => [],
162
+ getElementById: () => null,
163
+ getElementsByTagName: () => [],
164
+ getElementsByClassName: () => [],
165
+ cookie: "",
166
+ body: mockElement,
167
+ documentElement: mockElement,
168
+ addEventListener: noop,
169
+ removeEventListener: noop,
170
+ };
171
+
172
+ const mockNavigator = {
173
+ userAgent: "Mozilla/5.0 (Server-Side)",
174
+ language: "en-US",
175
+ languages: ["en-US"],
176
+ onLine: true,
177
+ clipboard: { writeText: async () => {}, readText: async () => "" },
178
+ };
179
+
180
+
181
+ globalThis.document = globalThis.document || mockDocument;
182
+ globalThis.navigator = globalThis.navigator || mockNavigator;
183
+ globalThis.localStorage = globalThis.localStorage || mockLocalStorage;
184
+ globalThis.sessionStorage = globalThis.sessionStorage || mockSessionStorage;
185
+
186
+ globalThis.window = new Proxy(globalThis, {
187
+ get: (target, prop) => {
188
+ if (prop in target) return target[prop];
189
+ if (prop === 'document') return globalThis.document;
190
+ if (prop === 'navigator') return globalThis.navigator;
191
+ if (prop === 'localStorage') return globalThis.localStorage;
192
+ if (prop === 'sessionStorage') return globalThis.sessionStorage;
193
+ if (prop === 'addEventListener' || prop === 'removeEventListener') return noop;
194
+ if (prop === 'location') return { href: '', search: '', pathname: '/', origin: '', assign: noop, replace: noop };
195
+ if (prop === 'matchMedia') return () => ({ matches: false, addEventListener: noop, removeEventListener: noop, dispatchEvent: noop });
196
+ if (prop === 'getComputedStyle') return () => ({ getPropertyValue: () => '' });
197
+ if (prop === 'requestAnimationFrame' || prop === 'cancelAnimationFrame') return (cb) => setTimeout(cb, 0);
198
+ return undefined;
199
+ }
200
+ });
201
+ }`
202
+ } : undefined,
63
203
  plugins: isNode ? [] : [nodeStubPlugin],
64
204
  external: isNode
65
205
  ? ['react', 'react-dom', 'react-dom/server', 'react/jsx-runtime', 'react/jsx-dev-runtime',
@@ -75,7 +215,15 @@ async function buildWithEsbuild(virtualContent, resolveDir, platform) {
75
215
  toFwdSlash(path.join(__dirname, 'rendererTempFiles.js'))
76
216
  ],
77
217
  });
78
- return result.outputFiles[0].text;
218
+ const jsFile = result.outputFiles.find((file) => path.resolve(file.path) === outfile);
219
+ const mapFile = result.outputFiles.find((file) => path.resolve(file.path) === `${outfile}.map`);
220
+ if (!jsFile) {
221
+ throw new Error(`esbuild did not produce ${outfile}`);
222
+ }
223
+ const code = mapFile && options.sourceMapUrl
224
+ ? `${jsFile.text}\n//# sourceMappingURL=${options.sourceMapUrl}\n`
225
+ : jsFile.text;
226
+ return { code, map: mapFile?.text };
79
227
  }
80
228
  export function findFileUpwards(startDir, relativePath) {
81
229
  let dir = path.resolve(startDir);
@@ -107,18 +255,86 @@ function resolveTemplate(appDir, autoPageDir, autoPaging) {
107
255
  }
108
256
  return nextToCli;
109
257
  }
258
+ let htmlPanelBundleCache;
259
+ async function getHtmlPanelClientBundle() {
260
+ if (!devPanelPath) {
261
+ throw new Error("[HostJS] DevPanel is not available.");
262
+ }
263
+ const mtimeMs = fs.statSync(devPanelPath).mtimeMs;
264
+ if (htmlPanelBundleCache?.path === devPanelPath &&
265
+ htmlPanelBundleCache.mtimeMs === mtimeMs) {
266
+ return htmlPanelBundleCache.bundle;
267
+ }
268
+ const clientEntry = `
269
+ import React from "react";
270
+ import { hydrateRoot } from "react-dom/client";
271
+ import DevPanel from ${JSON.stringify(devPanelPath)};
272
+
273
+ const panelRoot = document.getElementById("authtics-dev-panel-root");
274
+ if (panelRoot) {
275
+ hydrateRoot(panelRoot, React.createElement(DevPanel));
276
+ }
277
+ `;
278
+ const bundle = buildWithEsbuild(clientEntry, path.dirname(devPanelPath), "browser", false, { sourceMaps: false, minify: true }).then(({ code }) => code);
279
+ htmlPanelBundleCache = {
280
+ path: devPanelPath,
281
+ mtimeMs,
282
+ bundle,
283
+ };
284
+ return bundle;
285
+ }
286
+ export async function handleHTML(rawHtml, actuallyShowPanel, configDAT, userDAT, hotReload = false) {
287
+ const datMatches = configDAT === undefined ||
288
+ configDAT === userDAT ||
289
+ (Array.isArray(configDAT) && configDAT.includes(userDAT ?? ""));
290
+ const usesDevPanel = actuallyShowPanel &&
291
+ devPanelPath !== null &&
292
+ datMatches;
293
+ const configScript = `<script>window.__authtics_config__=` +
294
+ JSON.stringify({
295
+ datRestricted: configDAT !== undefined,
296
+ devMode: usesDevPanel,
297
+ }) +
298
+ `;</script>`;
299
+ let panelContainer = "";
300
+ let panelClientScript = "";
301
+ if (usesDevPanel && devPanelPath) {
302
+ const panelModule = await import(pathToFileURL(devPanelPath).href);
303
+ const Panel = (panelModule.default ?? panelModule.DevPanel);
304
+ const panelHtml = renderToString(React.createElement(Panel));
305
+ panelContainer =
306
+ `<div id="authtics-dev-panel-root">${panelHtml}</div>`;
307
+ const clientCode = await getHtmlPanelClientBundle();
308
+ const safeClientCode = clientCode.replace(/<\/script/gi, "<\\/script");
309
+ panelClientScript = `<script>${safeClientCode}</script>`;
310
+ }
311
+ const injection = [
312
+ panelContainer,
313
+ configScript,
314
+ panelClientScript,
315
+ hotReload ? HMR_SCRIPT : "",
316
+ ]
317
+ .filter(Boolean)
318
+ .join("\n");
319
+ const bodyClose = /<\/body\s*>/i;
320
+ if (bodyClose.test(rawHtml)) {
321
+ return rawHtml.replace(bodyClose, `${injection}\n$&`);
322
+ }
323
+ return `${rawHtml}\n${injection}`;
324
+ }
110
325
  function buildHtml(opts) {
111
- const { pageHtml, clientCode, devPanelContainer, templatePath, hotReload, fileName, datRestricted, devMode } = opts;
326
+ const { pageHtml, clientCode, devPanelContainer, templatePath, hotReload, fileName, datRestricted, devMode, renderMode } = opts;
112
327
  const configScript = `<script>window.__authtics_config__={datRestricted:${datRestricted ? 'true' : 'false'},devMode:${devMode !== false ? 'true' : 'false'}};</script>`;
113
328
  const hmrScript = hotReload ? HMR_SCRIPT : '';
329
+ const srtwsScript = renderMode === 'srtws' ? SRTWS_CLIENT_SCRIPT : '';
114
330
  if (templatePath && fs.existsSync(templatePath)) {
115
331
  let template = fs.readFileSync(templatePath, "utf-8");
116
332
  const contentTag = /<authtics_host_content\s*\/>/i;
117
333
  if (contentTag.test(template)) {
118
334
  template = template.replace(contentTag, `<div id="root">${pageHtml}</div>\n ${devPanelContainer}`);
119
335
  template = template.includes("</body>")
120
- ? template.replace("</body>", ` ${configScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}</body>`)
121
- : template + `\n${configScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}`;
336
+ ? template.replace("</body>", ` ${configScript}${srtwsScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}</body>`)
337
+ : template + `\n${configScript}${srtwsScript}${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}\n${hmrScript}`;
122
338
  return template;
123
339
  }
124
340
  console.warn('[authtics] _template.html has no <authtics_host_content /> — using default layout.');
@@ -133,16 +349,24 @@ function buildHtml(opts) {
133
349
  <div id="root">${pageHtml}</div>
134
350
  ${devPanelContainer}
135
351
  ${configScript}
136
- ${fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`}
352
+ ${renderMode === 'srtws' ? '' : (fileName ? `<script src="${fileName}"></script>` : `<script>${clientCode}</script>`)}
353
+ ${srtwsScript}
137
354
  ${hmrScript}
138
355
  </body>
139
356
  </html>`;
140
357
  }
141
- function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath) {
142
- if (!fs.existsSync(tempPath) || !fs.existsSync(serverCjsPath))
358
+ function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath, clientBundlePath, serverBundleRequired = true, requiredArtifacts = [], checkArtifactFreshness = true) {
359
+ if (!fs.existsSync(tempPath) || (serverBundleRequired && !fs.existsSync(serverCjsPath)))
360
+ return false;
361
+ if (clientBundlePath && !fs.existsSync(clientBundlePath))
362
+ return false;
363
+ if (requiredArtifacts.some((artifactPath) => !fs.existsSync(artifactPath)))
143
364
  return false;
144
365
  try {
145
366
  const cacheMtime = fs.statSync(tempPath).mtimeMs;
367
+ if (checkArtifactFreshness &&
368
+ requiredArtifacts.some((artifactPath) => fs.statSync(artifactPath).mtimeMs < cacheMtime))
369
+ return false;
146
370
  if (fs.statSync(tsxPath).mtimeMs > cacheMtime)
147
371
  return false;
148
372
  if (templatePath && fs.existsSync(templatePath) && fs.statSync(templatePath).mtimeMs > cacheMtime)
@@ -155,62 +379,128 @@ function isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPa
155
379
  return false;
156
380
  }
157
381
  }
158
- const HMR_SCRIPT = `<script>(function(){
159
- var reloading=false;
160
- window.addEventListener('beforeunload', function() {
161
- reloading = true;
162
- });
382
+ const HMR_SCRIPT = `<script>
383
+ (function () {
384
+ var es = null;
385
+ var disposed = false;
386
+ var reloading = false;
163
387
 
164
- var es=new EventSource('/hmr');
165
- es.onmessage=function(e){
166
- if(e.data==='reload'&&!reloading){
167
- reloading=true;
388
+ function connect() {
389
+ if (disposed || reloading || es) return;
390
+
391
+ es = new EventSource('/hmr');
392
+
393
+ es.onopen = function () { };
394
+
395
+ es.onmessage = function (event) {
396
+ if (event.data !== 'reload' || disposed || reloading) return;
397
+
398
+ reloading = true;
399
+
400
+ es.onmessage = null;
401
+ es.onerror = null;
168
402
  es.close();
403
+ es = null;
404
+
169
405
  window.location.reload();
406
+ };
407
+
408
+ es.onerror = function () {
409
+ if (disposed || reloading) return;
410
+ };
411
+ }
412
+
413
+ function dispose() {
414
+ if (disposed) return;
415
+
416
+ disposed = true;
417
+
418
+ if (es) {
419
+ es.onmessage = null;
420
+ es.onerror = null;
421
+ es.close();
422
+ es = null;
170
423
  }
171
- };
172
- es.onerror=function(){
173
- if(!reloading){
174
- setTimeout(function(){
175
- if(!reloading){
176
- reloading=true;
177
- window.location.reload();
178
- }
179
- },1500);
180
- }
181
- };
182
- })();</script>`;
183
- export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true, autoPageDir, appDir, fileHandler, rmvFileHandler, reqDAT, configDAT) {
424
+ }
425
+ window.addEventListener('pagehide', dispose, { once: true });
426
+ window.addEventListener('unload', dispose, { once: true });
427
+
428
+ connect();
429
+ })();
430
+ </script>`;
431
+ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true, autoPageDir, appDir, fileHandler, rmvFileHandler, reqDAT, configDAT, renderMode = 'ssr', patchForServer = false, prebuildOnly = false) {
184
432
  ensureTempDirs();
185
433
  if (!fs.existsSync(tsxPath)) {
186
434
  throw new Error(`renderPage: source file not found: ${tsxPath}`);
187
435
  }
188
- let usesDevPanel = showPanel && devPanelPath !== null;
189
- if (usesDevPanel) {
190
- let datMatched = configDAT === undefined;
191
- if (configDAT && configDAT === reqDAT)
192
- datMatched = true;
193
- if (configDAT && Array.isArray(configDAT) && configDAT.includes(reqDAT ?? ""))
194
- datMatched = true;
195
- if (!datMatched)
196
- usesDevPanel = false;
197
- }
436
+ const datMatches = configDAT === undefined ||
437
+ configDAT === reqDAT ||
438
+ (Array.isArray(configDAT) && configDAT.includes(reqDAT ?? ""));
439
+ const usesDevPanel = showPanel &&
440
+ devPanelPath !== null &&
441
+ datMatches;
442
+ const enableDiagnostics = usesDevPanel;
198
443
  const templatePath = resolveTemplate(appDir, autoPageDir, autoPaging);
199
- const tempPath = tempFileForTsx(tsxPath);
200
- const serverCjsPath = tempPath.replace(/\.json$/, ".server.cjs");
444
+ const cacheVariant = [
445
+ usesDevPanel ? "panel" : "normal",
446
+ enableDiagnostics ? "mapped" : "fast",
447
+ compiler,
448
+ ].join("-");
449
+ const tempPath = tempFileForTsx(tsxPath, cacheVariant);
450
+ const separateClientBundle = getBuildStyle() === "super";
451
+ const clientBundlePath = clientBundleForTsx(tsxPath, cacheVariant);
452
+ const serverCjsPath = serverBundleForTsx(tsxPath, cacheVariant, tempPath);
453
+ const ssgHtmlPath = ssgHtmlForTsx(tsxPath, cacheVariant, tempPath);
454
+ const requiredClientBundlePath = separateClientBundle ? clientBundlePath : undefined;
455
+ const serverCodeDisabled = renderMode === 'csr' || renderMode === 'srtws';
456
+ const clientJsUrl = `/${path.basename(tempPath).replace(".json", ".js").replace(".tsx_", ".").toLowerCase()}`;
457
+ const clientMapUrl = `${clientJsUrl}.map`;
458
+ const clientMapPath = `${clientBundlePath}.map`;
459
+ const serverMapPath = `${serverCjsPath}.map`;
460
+ const requiredMapArtifacts = enableDiagnostics && renderMode !== "srtws"
461
+ ? [clientMapPath, ...(!serverCodeDisabled ? [serverMapPath] : [])]
462
+ : [];
463
+ if (renderMode === 'ssg' &&
464
+ isCacheValid(tsxPath, ssgHtmlPath, serverCjsPath, templatePath, devPanelPath, requiredClientBundlePath, !serverCodeDisabled, requiredMapArtifacts, false)) {
465
+ try {
466
+ return fs.readFileSync(ssgHtmlPath, "utf-8");
467
+ }
468
+ catch {
469
+ }
470
+ }
201
471
  let clientCode;
472
+ let clientMap;
202
473
  let serverCode;
474
+ let serverMap;
203
475
  let freshWrite = false;
204
- if (isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath) && autoPaging) {
476
+ if (isCacheValid(tsxPath, tempPath, serverCjsPath, templatePath, devPanelPath, requiredClientBundlePath, !serverCodeDisabled, requiredMapArtifacts) &&
477
+ (autoPaging || getBuildTrigger() === "prebuilt")) {
205
478
  try {
206
479
  const parsed = JSON.parse(readTempFile(tempPath));
207
- clientCode = parsed.clientCode;
208
- serverCode = fs.readFileSync(serverCjsPath, "utf-8");
480
+ if (separateClientBundle) {
481
+ const expectedBundleRef = path.relative(process.cwd(), clientBundlePath);
482
+ clientCode = parsed.clientBundle === expectedBundleRef
483
+ ? fs.readFileSync(clientBundlePath, "utf-8")
484
+ : undefined;
485
+ }
486
+ else {
487
+ clientCode = parsed.clientCode;
488
+ }
489
+ if (enableDiagnostics && renderMode !== "srtws") {
490
+ clientMap = fs.readFileSync(clientMapPath, "utf-8");
491
+ if (!serverCodeDisabled)
492
+ serverMap = fs.readFileSync(serverMapPath, "utf-8");
493
+ }
494
+ serverCode = serverCodeDisabled ? undefined : fs.readFileSync(serverCjsPath, "utf-8");
209
495
  }
210
496
  catch {
497
+ clientCode = undefined;
498
+ clientMap = undefined;
499
+ serverCode = undefined;
500
+ serverMap = undefined;
211
501
  }
212
502
  }
213
- if (!clientCode || !serverCode) {
503
+ if (clientCode === undefined || (clientCode === "" && renderMode !== "srtws") || (!serverCode && !serverCodeDisabled)) {
214
504
  const virtualServer = `
215
505
  ${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
216
506
  import Page from ${JSON.stringify(tsxPath)};
@@ -219,49 +509,109 @@ export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = '
219
509
  `;
220
510
  const virtualClient = `
221
511
  import React from "react";
222
- import { hydrateRoot } from "react-dom/client";
512
+ import { ${renderMode === 'csr' ? 'createRoot' : 'hydrateRoot'} } from "react-dom/client";
223
513
  ${usesDevPanel ? `import DevPanel from ${JSON.stringify(devPanelPath)};` : ""}
224
514
  import Page from ${JSON.stringify(tsxPath)};
225
515
 
226
516
  ${usesDevPanel ? `
227
517
  const panelEl = document.getElementById("authtics-dev-panel-root");
228
- if (panelEl) hydrateRoot(panelEl, React.createElement(DevPanel));
518
+ if (panelEl) ${renderMode === 'csr' ? `createRoot(panelEl).render(React.createElement(DevPanel));` : `hydrateRoot(panelEl, React.createElement(DevPanel));`}
229
519
  ` : ""}
230
520
  const rootEl = document.getElementById("root");
231
- if (rootEl) hydrateRoot(rootEl, React.createElement(Page));
521
+ if (rootEl) {
522
+ ${renderMode === 'csr'
523
+ ? `createRoot(rootEl).render(React.createElement(Page));`
524
+ : `hydrateRoot(rootEl, React.createElement(Page));`}
525
+ }
232
526
  `;
233
527
  if (compiler === 'esbuild') {
234
- serverCode = await buildWithEsbuild(virtualServer, process.cwd(), 'node');
235
- clientCode = await buildWithEsbuild(virtualClient, process.cwd(), 'browser');
528
+ if (!serverCodeDisabled) {
529
+ const builtServer = await buildWithEsbuild(virtualServer, process.cwd(), 'node', patchForServer, {
530
+ sourceMaps: enableDiagnostics,
531
+ minify: !enableDiagnostics,
532
+ outfile: serverCjsPath,
533
+ sourceMapUrl: path.basename(serverMapPath),
534
+ });
535
+ serverCode = builtServer.code;
536
+ serverMap = builtServer.map;
537
+ }
538
+ if (renderMode !== 'srtws') {
539
+ const built = await buildWithEsbuild(virtualClient, process.cwd(), 'browser', patchForServer, {
540
+ sourceMaps: enableDiagnostics,
541
+ minify: !enableDiagnostics,
542
+ outfile: clientBundlePath,
543
+ sourceMapUrl: clientMapUrl,
544
+ });
545
+ clientCode = built.code;
546
+ clientMap = built.map;
547
+ }
548
+ else {
549
+ clientCode = "";
550
+ }
551
+ }
552
+ if (serverCode)
553
+ await fs.promises.writeFile(serverCjsPath, serverCode, "utf-8");
554
+ if (separateClientBundle) {
555
+ await fs.promises.writeFile(clientBundlePath, clientCode ?? "", "utf-8");
556
+ writeTempFile(tempPath, JSON.stringify({
557
+ clientBundle: path.relative(process.cwd(), clientBundlePath),
558
+ }));
559
+ }
560
+ else {
561
+ writeTempFile(tempPath, JSON.stringify({ clientCode }));
562
+ }
563
+ if (serverMap)
564
+ await fs.promises.writeFile(serverMapPath, serverMap, "utf-8");
565
+ if (clientMap)
566
+ await fs.promises.writeFile(clientMapPath, clientMap, "utf-8");
567
+ if (requiredMapArtifacts.includes(clientMapPath) && !clientMap) {
568
+ throw new Error(`[HostJS] Source-map build did not produce ${clientMapPath}`);
569
+ }
570
+ if (requiredMapArtifacts.includes(serverMapPath) && !serverMap) {
571
+ throw new Error(`[HostJS] Source-map build did not produce ${serverMapPath}`);
236
572
  }
237
- await fs.promises.writeFile(serverCjsPath, serverCode || "", "utf-8");
238
- writeTempFile(tempPath, JSON.stringify({ clientCode }));
239
573
  freshWrite = true;
240
574
  }
241
- fileHandler ? fileHandler(clientCode || "", `/${path.basename(tempPath).replace(".json", ".js").replace(".tsx_", ".").toLowerCase()}`) : null;
242
- const absoluteServerPath = path.resolve(serverCjsPath);
243
- delete customRequire.cache[absoluteServerPath];
244
- const serverModule = customRequire(absoluteServerPath);
245
- const exports = serverModule.__esModule ? serverModule.default : serverModule;
246
- const PageComponent = exports?.default?.default ??
247
- exports?.default ??
248
- exports?.Page ??
249
- exports?.page ??
250
- (typeof exports === 'function' ? exports : null);
251
- const DevPanelComponent = usesDevPanel ? (exports.DevPanel ?? null) : null;
252
- if (!PageComponent) {
253
- throw new Error(`renderPage: no default export found in ${tsxPath}`);
254
- }
255
- const pageHtml = renderToString(React.createElement(PageComponent));
256
- const devPanelHtml = DevPanelComponent
257
- ? renderToString(React.createElement(DevPanelComponent))
258
- : "";
259
- const devPanelContainer = usesDevPanel
260
- ? `<div id="authtics-dev-panel-root">${devPanelHtml}</div>`
575
+ if (clientCode !== undefined)
576
+ fileHandler?.(clientCode, clientJsUrl);
577
+ if (clientMap !== undefined)
578
+ fileHandler?.(clientMap, clientMapUrl);
579
+ if (prebuildOnly && renderMode !== 'ssg')
580
+ return "";
581
+ let pageHtml = "";
582
+ let devPanelContainer = usesDevPanel
583
+ ? `<div id="authtics-dev-panel-root"></div>`
261
584
  : "";
262
- return buildHtml({ pageHtml, clientCode: clientCode || "", devPanelContainer, templatePath, hotReload,
585
+ if (!serverCodeDisabled) {
586
+ const absoluteServerPath = path.resolve(serverCjsPath);
587
+ delete customRequire.cache[absoluteServerPath];
588
+ const serverModule = customRequire(absoluteServerPath);
589
+ const exports = serverModule.__esModule ? serverModule.default : serverModule;
590
+ const PageComponent = exports?.default?.default ??
591
+ exports?.default ??
592
+ exports?.Page ??
593
+ exports?.page ??
594
+ (typeof exports === 'function' ? exports : null);
595
+ const DevPanelComponent = usesDevPanel ? (exports.DevPanel ?? null) : null;
596
+ if (!PageComponent) {
597
+ throw new Error(`renderPage: no default export found in ${tsxPath}`);
598
+ }
599
+ pageHtml = renderToString(React.createElement(PageComponent));
600
+ const devPanelHtml = DevPanelComponent
601
+ ? renderToString(React.createElement(DevPanelComponent))
602
+ : "";
603
+ devPanelContainer = usesDevPanel
604
+ ? `<div id="authtics-dev-panel-root">${devPanelHtml}</div>`
605
+ : "";
606
+ }
607
+ const finalHtml = buildHtml({ pageHtml, clientCode: clientCode || "", devPanelContainer, templatePath, hotReload,
263
608
  fileName: `/${path.basename(tempPath).replace(".json", ".js").replace(".tsx_", ".").toLowerCase()}`,
264
609
  datRestricted: configDAT !== undefined,
265
610
  devMode: showPanel,
611
+ renderMode
266
612
  });
613
+ if (renderMode === 'ssg') {
614
+ await fs.promises.writeFile(ssgHtmlPath, finalHtml, "utf-8");
615
+ }
616
+ return finalHtml;
267
617
  }