@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/.agents/memory/MEMORY.md +1 -0
- package/.agents/memory/node-child-process-tests.md +10 -0
- package/README.md +145 -36
- package/assets/dev_panel.jpg +0 -0
- package/assets/result.jpg +0 -0
- package/dist/BrowserAPIs.d.ts +10 -0
- package/dist/BrowserAPIs.js +53 -0
- package/dist/cli.d.ts +5 -3
- package/dist/cli.js +427 -67
- package/dist/config.d.ts +8 -1
- package/dist/config.js +20 -0
- package/dist/devPanel.d.ts +11 -0
- package/dist/devPanel.js +415 -59
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/renderEngine.d.ts +5 -1
- package/dist/renderEngine.js +429 -79
- package/dist/rendererTempFiles.d.ts +10 -1
- package/dist/rendererTempFiles.js +101 -17
- package/dist/superBuildOutput.d.ts +9 -0
- package/dist/superBuildOutput.js +175 -0
- package/dist/svg_assets/slope_top_left.d.ts +1 -0
- package/dist/svg_assets/slope_top_left.js +4 -0
- package/package.json +23 -11
- package/scripts/post-merge.sh +5 -0
- package/svg_assets/slope_top_left.tsx +19 -0
- package/tests/source-map-access.test.mjs +167 -0
- package/tests/source-map-location.test.mjs +237 -0
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
|
-
|
|
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
|
-
|
|
4
|
+
import { load as loadBAPI, Cookies } from "./BrowserAPIs.js";
|
|
5
|
+
export { App, Route, RouteMap, Paused, defineConfig, loadBAPI, Cookies };
|
package/dist/renderEngine.d.ts
CHANGED
|
@@ -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
|
|
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>;
|
package/dist/renderEngine.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
382
|
+
const HMR_SCRIPT = `<script>
|
|
383
|
+
(function () {
|
|
384
|
+
var es = null;
|
|
385
|
+
var disposed = false;
|
|
386
|
+
var reloading = false;
|
|
163
387
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
200
|
-
|
|
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
|
|
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
|
-
|
|
208
|
-
|
|
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 (
|
|
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)
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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
|
}
|