nukejs 0.0.29 → 0.0.31
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/builder.js +1 -0
- package/dist/render-component.js +23 -0
- package/dist/render-document.js +165 -0
- package/dist/renderer.js +3 -5
- package/dist/server.d.ts +13 -0
- package/dist/server.js +4 -0
- package/dist/ssr.js +16 -147
- package/package.json +5 -1
package/dist/builder.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { renderDocument } from "./render-document.js";
|
|
3
|
+
async function renderComponent(Component, props = {}, options = {}) {
|
|
4
|
+
const layouts = options.layouts ?? [];
|
|
5
|
+
let element = createElement(Component, props);
|
|
6
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
7
|
+
element = createElement(layouts[i], { children: element });
|
|
8
|
+
}
|
|
9
|
+
return renderDocument({
|
|
10
|
+
element,
|
|
11
|
+
// No clientRegistry / resolveComponentCache — pure SSR, see module doc.
|
|
12
|
+
url: options.url ?? "/",
|
|
13
|
+
params: options.params,
|
|
14
|
+
query: options.query,
|
|
15
|
+
headers: options.headers,
|
|
16
|
+
isDev: options.isDev ?? process.env.ENVIRONMENT !== "production",
|
|
17
|
+
skipClientSSR: false,
|
|
18
|
+
defaultTitle: options.title ?? "NukeJS"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
renderComponent
|
|
23
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { renderElementToHtml } from "./renderer.js";
|
|
2
|
+
import { runWithRequestStore, normaliseHeaders, sanitiseHeaders } from "./request-store.js";
|
|
3
|
+
import { runWithCacheStore } from "./cache-store.js";
|
|
4
|
+
import {
|
|
5
|
+
runWithHtmlStore,
|
|
6
|
+
resolveTitle
|
|
7
|
+
} from "./html-store.js";
|
|
8
|
+
import { getDebugLevel } from "./logger.js";
|
|
9
|
+
function toClientDebugLevel(level) {
|
|
10
|
+
if (level === true) return "verbose";
|
|
11
|
+
if (level === "info") return "info";
|
|
12
|
+
if (level === "error") return "error";
|
|
13
|
+
return "silent";
|
|
14
|
+
}
|
|
15
|
+
function escapeAttr(str) {
|
|
16
|
+
return str.replace(/&/g, "&").replace(/"/g, """);
|
|
17
|
+
}
|
|
18
|
+
function renderAttrs(attrs) {
|
|
19
|
+
return Object.entries(attrs).filter(([, v]) => v !== void 0 && v !== false).map(([k, v]) => v === true ? k : `${k}="${escapeAttr(String(v))}"`).join(" ");
|
|
20
|
+
}
|
|
21
|
+
function openTag(tag, attrs) {
|
|
22
|
+
const str = renderAttrs(attrs);
|
|
23
|
+
return str ? `<${tag} ${str}>` : `<${tag}>`;
|
|
24
|
+
}
|
|
25
|
+
function metaKey(k) {
|
|
26
|
+
return k === "httpEquiv" ? "http-equiv" : k;
|
|
27
|
+
}
|
|
28
|
+
function linkKey(k) {
|
|
29
|
+
if (k === "hrefLang") return "hreflang";
|
|
30
|
+
if (k === "crossOrigin") return "crossorigin";
|
|
31
|
+
return k;
|
|
32
|
+
}
|
|
33
|
+
function renderMetaTag(tag) {
|
|
34
|
+
const attrs = {};
|
|
35
|
+
for (const [k, v] of Object.entries(tag)) if (v !== void 0) attrs[metaKey(k)] = v;
|
|
36
|
+
return ` <meta ${renderAttrs(attrs)} />`;
|
|
37
|
+
}
|
|
38
|
+
function renderLinkTag(tag) {
|
|
39
|
+
const attrs = {};
|
|
40
|
+
for (const [k, v] of Object.entries(tag)) if (v !== void 0) attrs[linkKey(k)] = v;
|
|
41
|
+
return ` <link ${renderAttrs(attrs)} />`;
|
|
42
|
+
}
|
|
43
|
+
function renderScriptTag(tag) {
|
|
44
|
+
const attrs = {
|
|
45
|
+
src: tag.src,
|
|
46
|
+
type: tag.type,
|
|
47
|
+
crossorigin: tag.crossOrigin,
|
|
48
|
+
integrity: tag.integrity,
|
|
49
|
+
defer: tag.defer,
|
|
50
|
+
async: tag.async,
|
|
51
|
+
nomodule: tag.noModule
|
|
52
|
+
};
|
|
53
|
+
const attrStr = renderAttrs(attrs);
|
|
54
|
+
const open = attrStr ? `<script ${attrStr}>` : "<script>";
|
|
55
|
+
return ` ${open}${tag.src ? "" : tag.content ?? ""}</script>`;
|
|
56
|
+
}
|
|
57
|
+
function renderStyleTag(tag) {
|
|
58
|
+
const media = tag.media ? ` media="${escapeAttr(tag.media)}"` : "";
|
|
59
|
+
return ` <style${media}>${tag.content ?? ""}</style>`;
|
|
60
|
+
}
|
|
61
|
+
function renderManagedHeadTags(store) {
|
|
62
|
+
const headScripts = store.script.filter((s) => (s.position ?? "head") === "head");
|
|
63
|
+
const tags = [
|
|
64
|
+
...store.meta.map(renderMetaTag),
|
|
65
|
+
...store.link.map(renderLinkTag),
|
|
66
|
+
...store.style.map(renderStyleTag),
|
|
67
|
+
...headScripts.map(renderScriptTag)
|
|
68
|
+
];
|
|
69
|
+
if (tags.length === 0) return [];
|
|
70
|
+
return [" <!--n-head-->", ...tags, " <!--/n-head-->"];
|
|
71
|
+
}
|
|
72
|
+
function renderManagedBodyScripts(store) {
|
|
73
|
+
const bodyScripts = store.script.filter((s) => s.position === "body");
|
|
74
|
+
if (bodyScripts.length === 0) return [];
|
|
75
|
+
return [" <!--n-body-scripts-->", ...bodyScripts.map(renderScriptTag), " <!--/n-body-scripts-->"];
|
|
76
|
+
}
|
|
77
|
+
async function renderDocument(options) {
|
|
78
|
+
const {
|
|
79
|
+
element,
|
|
80
|
+
clientRegistry,
|
|
81
|
+
resolveComponentCache,
|
|
82
|
+
url,
|
|
83
|
+
params = {},
|
|
84
|
+
query = {},
|
|
85
|
+
headers = {},
|
|
86
|
+
isDev = false,
|
|
87
|
+
skipClientSSR = false,
|
|
88
|
+
defaultTitle = "NukeJS"
|
|
89
|
+
} = options;
|
|
90
|
+
const cleanUrl = url.split("?")[0];
|
|
91
|
+
const normHeaders = normaliseHeaders(headers);
|
|
92
|
+
const safeHeaders = sanitiseHeaders(headers);
|
|
93
|
+
const registry = clientRegistry ?? /* @__PURE__ */ new Map();
|
|
94
|
+
const ctx = {
|
|
95
|
+
registry,
|
|
96
|
+
hydrated: /* @__PURE__ */ new Set(),
|
|
97
|
+
skipClientSSR,
|
|
98
|
+
getComponentCache: resolveComponentCache
|
|
99
|
+
};
|
|
100
|
+
let appHtml = "";
|
|
101
|
+
const store = await runWithRequestStore(
|
|
102
|
+
{
|
|
103
|
+
url,
|
|
104
|
+
pathname: cleanUrl,
|
|
105
|
+
params,
|
|
106
|
+
query,
|
|
107
|
+
headers: normHeaders
|
|
108
|
+
},
|
|
109
|
+
() => runWithCacheStore(() => runWithHtmlStore(async () => {
|
|
110
|
+
appHtml = await renderElementToHtml(element, ctx);
|
|
111
|
+
}))
|
|
112
|
+
);
|
|
113
|
+
const pageTitle = resolveTitle(store.titleOps, defaultTitle);
|
|
114
|
+
const headLines = [
|
|
115
|
+
' <meta charset="utf-8" />',
|
|
116
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
117
|
+
` <title>${escapeAttr(pageTitle)}</title>`,
|
|
118
|
+
...renderManagedHeadTags(store)
|
|
119
|
+
];
|
|
120
|
+
const runtimeData = JSON.stringify({
|
|
121
|
+
hydrateIds: [...ctx.hydrated],
|
|
122
|
+
allIds: [...registry.keys()],
|
|
123
|
+
url,
|
|
124
|
+
params,
|
|
125
|
+
query,
|
|
126
|
+
headers: safeHeaders,
|
|
127
|
+
debug: toClientDebugLevel(getDebugLevel())
|
|
128
|
+
}).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
129
|
+
const bodyScriptLines = renderManagedBodyScripts(store);
|
|
130
|
+
const bodyScriptsHtml = bodyScriptLines.length > 0 ? "\n" + bodyScriptLines.join("\n") + "\n" : "";
|
|
131
|
+
return `<!DOCTYPE html>
|
|
132
|
+
${openTag("html", store.htmlAttrs)}
|
|
133
|
+
<head>
|
|
134
|
+
${headLines.join("\n")}
|
|
135
|
+
</head>
|
|
136
|
+
${openTag("body", store.bodyAttrs)}
|
|
137
|
+
<div id="app">${appHtml}</div>
|
|
138
|
+
|
|
139
|
+
<script id="__n_data" type="application/json">${runtimeData}</script>
|
|
140
|
+
|
|
141
|
+
<script type="importmap">
|
|
142
|
+
{
|
|
143
|
+
"imports": {
|
|
144
|
+
"react": "/__react.js",
|
|
145
|
+
"react-dom/client": "/__react.js",
|
|
146
|
+
"react/jsx-runtime": "/__react.js",
|
|
147
|
+
"nukejs": "/__n.js"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<script type="module">
|
|
153
|
+
await import('react');
|
|
154
|
+
const { initRuntime } = await import('nukejs');
|
|
155
|
+
const data = JSON.parse(document.getElementById('__n_data').textContent);
|
|
156
|
+
initRuntime(data);
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
${isDev ? '<script type="module" src="/__hmr.js"></script>' : ""}
|
|
160
|
+
${bodyScriptsHtml}</body>
|
|
161
|
+
</html>`;
|
|
162
|
+
}
|
|
163
|
+
export {
|
|
164
|
+
renderDocument
|
|
165
|
+
};
|
package/dist/renderer.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import path from "path";
|
|
2
1
|
import { createElement, Fragment } from "react";
|
|
3
2
|
import { renderToString } from "react-dom/server";
|
|
4
3
|
import { log } from "./logger.js";
|
|
5
|
-
import { getComponentCache } from "./component-analyzer.js";
|
|
6
4
|
import { escapeHtml } from "./utils.js";
|
|
7
5
|
function isWrapperAttr(key) {
|
|
8
6
|
return key === "className" || key === "style" || key === "id" || key.startsWith("data-") || key.startsWith("aria-");
|
|
@@ -77,7 +75,7 @@ async function renderHtmlElement(type, props, ctx) {
|
|
|
77
75
|
return `<${type}${attrStr}>${childrenHtml}</${type}>`;
|
|
78
76
|
}
|
|
79
77
|
async function renderFunctionComponent(type, props, ctx) {
|
|
80
|
-
const componentCache = getComponentCache();
|
|
78
|
+
const componentCache = ctx.getComponentCache ? ctx.getComponentCache() : /* @__PURE__ */ new Map();
|
|
81
79
|
for (const [id, filePath] of ctx.registry.entries()) {
|
|
82
80
|
const info = componentCache.get(filePath);
|
|
83
81
|
if (!info?.isClientComponent) continue;
|
|
@@ -91,7 +89,7 @@ async function renderFunctionComponent(type, props, ctx) {
|
|
|
91
89
|
const { wrapperAttrs, componentProps } = splitWrapperAttrs(props);
|
|
92
90
|
const wrapperAttrStr = buildWrapperAttrString(wrapperAttrs);
|
|
93
91
|
const { real: hydrationSafeProps, json: serializedProps } = await prepareProps(componentProps, ctx);
|
|
94
|
-
log.verbose(`Client component rendered for hydration: ${id} (${
|
|
92
|
+
log.verbose(`Client component rendered for hydration: ${id} (${filePath.split(/[\\/]/).pop()})`);
|
|
95
93
|
const html = ctx.skipClientSSR ? "" : renderToString(createElement(type, hydrationSafeProps));
|
|
96
94
|
return `<span data-hydrate-id="${id}"${wrapperAttrStr} data-hydrate-props="${escapeHtml(
|
|
97
95
|
JSON.stringify(serializedProps)
|
|
@@ -148,7 +146,7 @@ async function prepareElement(element, ctx) {
|
|
|
148
146
|
return { real: createElement(type, p.real), json: { __re: "html", tag: type, props: p.json } };
|
|
149
147
|
}
|
|
150
148
|
if (typeof type === "function") {
|
|
151
|
-
const componentCache = getComponentCache();
|
|
149
|
+
const componentCache = ctx.getComponentCache ? ctx.getComponentCache() : /* @__PURE__ */ new Map();
|
|
152
150
|
for (const [id, filePath] of ctx.registry.entries()) {
|
|
153
151
|
const info = componentCache.get(filePath);
|
|
154
152
|
if (!info?.isClientComponent) continue;
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server.ts — server-only public API.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from index.ts on purpose: index.ts is the entry point the
|
|
5
|
+
* client component bundler (bundler.ts, platform: 'browser') resolves
|
|
6
|
+
* whenever a "use client" file imports from 'nukejs'. renderComponent()
|
|
7
|
+
* pulls in tsx/esm/api and, transitively, ssr.ts's Node built-ins (fs,
|
|
8
|
+
* node:worker_threads, …) — none of which esbuild can (or should) resolve
|
|
9
|
+
* for a browser bundle. Anything server-only belongs here, under
|
|
10
|
+
* 'nukejs/server', not in the shared index.
|
|
11
|
+
*/
|
|
12
|
+
export { renderComponent } from './render-component';
|
|
13
|
+
export type { RenderComponentOptions } from './render-component';
|
package/dist/server.js
ADDED
package/dist/ssr.js
CHANGED
|
@@ -3,16 +3,10 @@ import fs from "fs";
|
|
|
3
3
|
import { createElement } from "react";
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
5
|
import { tsImport } from "tsx/esm/api";
|
|
6
|
-
import { log
|
|
6
|
+
import { log } from "./logger.js";
|
|
7
7
|
import { matchRoute, findLayoutsForRoute } from "./router.js";
|
|
8
|
-
import { findClientComponentsInTree } from "./component-analyzer.js";
|
|
9
|
-
import {
|
|
10
|
-
import { runWithRequestStore, normaliseHeaders, sanitiseHeaders } from "./request-store.js";
|
|
11
|
-
import { runWithCacheStore } from "./cache-store.js";
|
|
12
|
-
import {
|
|
13
|
-
runWithHtmlStore,
|
|
14
|
-
resolveTitle
|
|
15
|
-
} from "./html-store.js";
|
|
8
|
+
import { findClientComponentsInTree, getComponentCache } from "./component-analyzer.js";
|
|
9
|
+
import { renderDocument } from "./render-document.js";
|
|
16
10
|
async function wrapWithLayouts(pageElement, layoutPaths) {
|
|
17
11
|
let element = pageElement;
|
|
18
12
|
for (let i = layoutPaths.length - 1; i >= 0; i--) {
|
|
@@ -24,76 +18,7 @@ async function wrapWithLayouts(pageElement, layoutPaths) {
|
|
|
24
18
|
}
|
|
25
19
|
return element;
|
|
26
20
|
}
|
|
27
|
-
function toClientDebugLevel(level) {
|
|
28
|
-
if (level === true) return "verbose";
|
|
29
|
-
if (level === "info") return "info";
|
|
30
|
-
if (level === "error") return "error";
|
|
31
|
-
return "silent";
|
|
32
|
-
}
|
|
33
|
-
function escapeAttr(str) {
|
|
34
|
-
return str.replace(/&/g, "&").replace(/"/g, """);
|
|
35
|
-
}
|
|
36
|
-
function renderAttrs(attrs) {
|
|
37
|
-
return Object.entries(attrs).filter(([, v]) => v !== void 0 && v !== false).map(([k, v]) => v === true ? k : `${k}="${escapeAttr(String(v))}"`).join(" ");
|
|
38
|
-
}
|
|
39
|
-
function openTag(tag, attrs) {
|
|
40
|
-
const str = renderAttrs(attrs);
|
|
41
|
-
return str ? `<${tag} ${str}>` : `<${tag}>`;
|
|
42
|
-
}
|
|
43
|
-
function metaKey(k) {
|
|
44
|
-
return k === "httpEquiv" ? "http-equiv" : k;
|
|
45
|
-
}
|
|
46
|
-
function linkKey(k) {
|
|
47
|
-
if (k === "hrefLang") return "hreflang";
|
|
48
|
-
if (k === "crossOrigin") return "crossorigin";
|
|
49
|
-
return k;
|
|
50
|
-
}
|
|
51
|
-
function renderMetaTag(tag) {
|
|
52
|
-
const attrs = {};
|
|
53
|
-
for (const [k, v] of Object.entries(tag)) if (v !== void 0) attrs[metaKey(k)] = v;
|
|
54
|
-
return ` <meta ${renderAttrs(attrs)} />`;
|
|
55
|
-
}
|
|
56
|
-
function renderLinkTag(tag) {
|
|
57
|
-
const attrs = {};
|
|
58
|
-
for (const [k, v] of Object.entries(tag)) if (v !== void 0) attrs[linkKey(k)] = v;
|
|
59
|
-
return ` <link ${renderAttrs(attrs)} />`;
|
|
60
|
-
}
|
|
61
|
-
function renderScriptTag(tag) {
|
|
62
|
-
const attrs = {
|
|
63
|
-
src: tag.src,
|
|
64
|
-
type: tag.type,
|
|
65
|
-
crossorigin: tag.crossOrigin,
|
|
66
|
-
integrity: tag.integrity,
|
|
67
|
-
defer: tag.defer,
|
|
68
|
-
async: tag.async,
|
|
69
|
-
nomodule: tag.noModule
|
|
70
|
-
};
|
|
71
|
-
const attrStr = renderAttrs(attrs);
|
|
72
|
-
const open = attrStr ? `<script ${attrStr}>` : "<script>";
|
|
73
|
-
return ` ${open}${tag.src ? "" : tag.content ?? ""}</script>`;
|
|
74
|
-
}
|
|
75
|
-
function renderStyleTag(tag) {
|
|
76
|
-
const media = tag.media ? ` media="${escapeAttr(tag.media)}"` : "";
|
|
77
|
-
return ` <style${media}>${tag.content ?? ""}</style>`;
|
|
78
|
-
}
|
|
79
|
-
function renderManagedHeadTags(store) {
|
|
80
|
-
const headScripts = store.script.filter((s) => (s.position ?? "head") === "head");
|
|
81
|
-
const tags = [
|
|
82
|
-
...store.meta.map(renderMetaTag),
|
|
83
|
-
...store.link.map(renderLinkTag),
|
|
84
|
-
...store.style.map(renderStyleTag),
|
|
85
|
-
...headScripts.map(renderScriptTag)
|
|
86
|
-
];
|
|
87
|
-
if (tags.length === 0) return [];
|
|
88
|
-
return [" <!--n-head-->", ...tags, " <!--/n-head-->"];
|
|
89
|
-
}
|
|
90
|
-
function renderManagedBodyScripts(store) {
|
|
91
|
-
const bodyScripts = store.script.filter((s) => s.position === "body");
|
|
92
|
-
if (bodyScripts.length === 0) return [];
|
|
93
|
-
return [" <!--n-body-scripts-->", ...bodyScripts.map(renderScriptTag), " <!--/n-body-scripts-->"];
|
|
94
|
-
}
|
|
95
21
|
async function renderFile(filePath, params, url, pagesDir, isDev, res, req, statusCode, skipClientSSR) {
|
|
96
|
-
const cleanUrl = url.split("?")[0];
|
|
97
22
|
const searchParams = new URL(url, "http://localhost").searchParams;
|
|
98
23
|
const queryParams = {};
|
|
99
24
|
searchParams.forEach((_, k) => {
|
|
@@ -103,9 +28,6 @@ async function renderFile(filePath, params, url, pagesDir, isDev, res, req, stat
|
|
|
103
28
|
}
|
|
104
29
|
});
|
|
105
30
|
const mergedParams = { ...queryParams, ...params };
|
|
106
|
-
const rawHeaders = req?.headers ?? {};
|
|
107
|
-
const normHeaders = normaliseHeaders(rawHeaders);
|
|
108
|
-
const safeHeaders = sanitiseHeaders(rawHeaders);
|
|
109
31
|
const layoutPaths = findLayoutsForRoute(filePath, pagesDir);
|
|
110
32
|
const { default: PageComponent } = await tsImport(
|
|
111
33
|
pathToFileURL(filePath).href,
|
|
@@ -115,75 +37,22 @@ async function renderFile(filePath, params, url, pagesDir, isDev, res, req, stat
|
|
|
115
37
|
createElement(PageComponent, mergedParams),
|
|
116
38
|
layoutPaths
|
|
117
39
|
);
|
|
118
|
-
const
|
|
119
|
-
for (const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const store = await runWithRequestStore(
|
|
127
|
-
{
|
|
128
|
-
url,
|
|
129
|
-
pathname: cleanUrl,
|
|
130
|
-
params,
|
|
131
|
-
query: queryParams,
|
|
132
|
-
headers: normHeaders
|
|
133
|
-
},
|
|
134
|
-
() => runWithCacheStore(() => runWithHtmlStore(async () => {
|
|
135
|
-
appHtml = await renderElementToHtml(wrappedElement, ctx);
|
|
136
|
-
}))
|
|
137
|
-
);
|
|
138
|
-
const pageTitle = resolveTitle(store.titleOps, "NukeJS");
|
|
139
|
-
const headLines = [
|
|
140
|
-
' <meta charset="utf-8" />',
|
|
141
|
-
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
142
|
-
` <title>${escapeAttr(pageTitle)}</title>`,
|
|
143
|
-
...renderManagedHeadTags(store)
|
|
144
|
-
];
|
|
145
|
-
const runtimeData = JSON.stringify({
|
|
146
|
-
hydrateIds: [...ctx.hydrated],
|
|
147
|
-
allIds: [...registry.keys()],
|
|
40
|
+
const clientRegistry = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const entryFile of [filePath, ...layoutPaths])
|
|
42
|
+
for (const [id, p] of findClientComponentsInTree(entryFile, pagesDir))
|
|
43
|
+
clientRegistry.set(id, p);
|
|
44
|
+
const html = await renderDocument({
|
|
45
|
+
element: wrappedElement,
|
|
46
|
+
clientRegistry,
|
|
47
|
+
resolveComponentCache: getComponentCache,
|
|
148
48
|
url,
|
|
149
49
|
params,
|
|
150
50
|
query: queryParams,
|
|
151
|
-
headers:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const html = `<!DOCTYPE html>
|
|
157
|
-
${openTag("html", store.htmlAttrs)}
|
|
158
|
-
<head>
|
|
159
|
-
${headLines.join("\n")}
|
|
160
|
-
</head>
|
|
161
|
-
${openTag("body", store.bodyAttrs)}
|
|
162
|
-
<div id="app">${appHtml}</div>
|
|
163
|
-
|
|
164
|
-
<script id="__n_data" type="application/json">${runtimeData}</script>
|
|
165
|
-
|
|
166
|
-
<script type="importmap">
|
|
167
|
-
{
|
|
168
|
-
"imports": {
|
|
169
|
-
"react": "/__react.js",
|
|
170
|
-
"react-dom/client": "/__react.js",
|
|
171
|
-
"react/jsx-runtime": "/__react.js",
|
|
172
|
-
"nukejs": "/__n.js"
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
</script>
|
|
176
|
-
|
|
177
|
-
<script type="module">
|
|
178
|
-
await import('react');
|
|
179
|
-
const { initRuntime } = await import('nukejs');
|
|
180
|
-
const data = JSON.parse(document.getElementById('__n_data').textContent);
|
|
181
|
-
initRuntime(data);
|
|
182
|
-
</script>
|
|
183
|
-
|
|
184
|
-
${isDev ? '<script type="module" src="/__hmr.js"></script>' : ""}
|
|
185
|
-
${bodyScriptsHtml}</body>
|
|
186
|
-
</html>`;
|
|
51
|
+
headers: req?.headers ?? {},
|
|
52
|
+
isDev,
|
|
53
|
+
skipClientSSR,
|
|
54
|
+
defaultTitle: "NukeJS"
|
|
55
|
+
});
|
|
187
56
|
res.statusCode = statusCode;
|
|
188
57
|
res.setHeader("Content-Type", "text/html");
|
|
189
58
|
res.end(html);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukejs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"description": "A minimal, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"import": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./server": {
|
|
13
|
+
"import": "./dist/server.js",
|
|
14
|
+
"types": "./dist/server.d.ts"
|
|
11
15
|
}
|
|
12
16
|
},
|
|
13
17
|
"bin": {
|