nukejs 0.0.29 → 0.0.30
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 +2 -0
- package/dist/index.js +2 -0
- package/dist/render-component.js +32 -0
- package/dist/ssr.js +55 -31
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type { RequestContext } from './use-request';
|
|
|
9
9
|
export { normaliseHeaders, sanitiseHeaders, getRequestStore } from './request-store';
|
|
10
10
|
export { cache } from './cache-store';
|
|
11
11
|
export { default as Link } from './Link';
|
|
12
|
+
export { renderComponent } from './render-component';
|
|
13
|
+
export type { RenderComponentOptions } from './render-component';
|
|
12
14
|
export { setupLocationChangeMonitor, initRuntime } from './bundle';
|
|
13
15
|
export type { RuntimeData } from './bundle';
|
|
14
16
|
export { escapeHtml } from './utils';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { useRequest } from "./use-request.js";
|
|
|
5
5
|
import { normaliseHeaders, sanitiseHeaders, getRequestStore } from "./request-store.js";
|
|
6
6
|
import { cache } from "./cache-store.js";
|
|
7
7
|
import { default as default3 } from "./Link.js";
|
|
8
|
+
import { renderComponent } from "./render-component.js";
|
|
8
9
|
import { setupLocationChangeMonitor, initRuntime } from "./bundle.js";
|
|
9
10
|
import { escapeHtml } from "./utils.js";
|
|
10
11
|
import { ansi, c, log, setDebugLevel, getDebugLevel } from "./logger.js";
|
|
@@ -21,6 +22,7 @@ export {
|
|
|
21
22
|
initRuntime,
|
|
22
23
|
log,
|
|
23
24
|
normaliseHeaders,
|
|
25
|
+
renderComponent,
|
|
24
26
|
sanitiseHeaders,
|
|
25
27
|
setDebugLevel,
|
|
26
28
|
setupLocationChangeMonitor,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
import { tsImport } from "tsx/esm/api";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { renderDocument } from "./ssr.js";
|
|
6
|
+
async function renderComponent(filePath, props = {}, options = {}) {
|
|
7
|
+
const layoutPaths = options.layoutPaths ?? [];
|
|
8
|
+
const { default: Component } = await tsImport(pathToFileURL(filePath).href, { parentURL: import.meta.url });
|
|
9
|
+
let element = createElement(Component, props);
|
|
10
|
+
for (let i = layoutPaths.length - 1; i >= 0; i--) {
|
|
11
|
+
const { default: Layout } = await tsImport(pathToFileURL(layoutPaths[i]).href, { parentURL: import.meta.url });
|
|
12
|
+
element = createElement(Layout, { children: element });
|
|
13
|
+
}
|
|
14
|
+
return renderDocument({
|
|
15
|
+
registryEntryFiles: [filePath, ...layoutPaths],
|
|
16
|
+
pagesDir: options.pagesDir ?? path.resolve("./app/pages"),
|
|
17
|
+
element,
|
|
18
|
+
url: options.url ?? "/",
|
|
19
|
+
params: options.params,
|
|
20
|
+
query: options.query,
|
|
21
|
+
headers: options.headers,
|
|
22
|
+
isDev: options.isDev ?? process.env.ENVIRONMENT !== "production",
|
|
23
|
+
// Always false here: the whole point is a real server-rendered
|
|
24
|
+
// document for crawlers, not just a hydration target. skipClientSSR
|
|
25
|
+
// exists in renderDocument only for ssr.ts's dev-mode HMR fast path.
|
|
26
|
+
skipClientSSR: false,
|
|
27
|
+
defaultTitle: options.title ?? "NukeJS"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
renderComponent
|
|
32
|
+
};
|
package/dist/ssr.js
CHANGED
|
@@ -92,34 +92,25 @@ function renderManagedBodyScripts(store) {
|
|
|
92
92
|
if (bodyScripts.length === 0) return [];
|
|
93
93
|
return [" <!--n-body-scripts-->", ...bodyScripts.map(renderScriptTag), " <!--/n-body-scripts-->"];
|
|
94
94
|
}
|
|
95
|
-
async function
|
|
95
|
+
async function renderDocument(options) {
|
|
96
|
+
const {
|
|
97
|
+
registryEntryFiles,
|
|
98
|
+
pagesDir,
|
|
99
|
+
element,
|
|
100
|
+
url,
|
|
101
|
+
params = {},
|
|
102
|
+
query = {},
|
|
103
|
+
headers = {},
|
|
104
|
+
isDev = false,
|
|
105
|
+
skipClientSSR = false,
|
|
106
|
+
defaultTitle = "NukeJS"
|
|
107
|
+
} = options;
|
|
96
108
|
const cleanUrl = url.split("?")[0];
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
searchParams.forEach((_, k) => {
|
|
100
|
-
if (!(k in params)) {
|
|
101
|
-
const all = searchParams.getAll(k);
|
|
102
|
-
queryParams[k] = all.length > 1 ? all : all[0];
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
const mergedParams = { ...queryParams, ...params };
|
|
106
|
-
const rawHeaders = req?.headers ?? {};
|
|
107
|
-
const normHeaders = normaliseHeaders(rawHeaders);
|
|
108
|
-
const safeHeaders = sanitiseHeaders(rawHeaders);
|
|
109
|
-
const layoutPaths = findLayoutsForRoute(filePath, pagesDir);
|
|
110
|
-
const { default: PageComponent } = await tsImport(
|
|
111
|
-
pathToFileURL(filePath).href,
|
|
112
|
-
{ parentURL: import.meta.url }
|
|
113
|
-
);
|
|
114
|
-
const wrappedElement = await wrapWithLayouts(
|
|
115
|
-
createElement(PageComponent, mergedParams),
|
|
116
|
-
layoutPaths
|
|
117
|
-
);
|
|
109
|
+
const normHeaders = normaliseHeaders(headers);
|
|
110
|
+
const safeHeaders = sanitiseHeaders(headers);
|
|
118
111
|
const registry = /* @__PURE__ */ new Map();
|
|
119
|
-
for (const
|
|
120
|
-
|
|
121
|
-
for (const layoutPath of layoutPaths)
|
|
122
|
-
for (const [id, p] of findClientComponentsInTree(layoutPath, pagesDir))
|
|
112
|
+
for (const entryFile of registryEntryFiles)
|
|
113
|
+
for (const [id, p] of findClientComponentsInTree(entryFile, pagesDir))
|
|
123
114
|
registry.set(id, p);
|
|
124
115
|
const ctx = { registry, hydrated: /* @__PURE__ */ new Set(), skipClientSSR };
|
|
125
116
|
let appHtml = "";
|
|
@@ -128,14 +119,14 @@ async function renderFile(filePath, params, url, pagesDir, isDev, res, req, stat
|
|
|
128
119
|
url,
|
|
129
120
|
pathname: cleanUrl,
|
|
130
121
|
params,
|
|
131
|
-
query
|
|
122
|
+
query,
|
|
132
123
|
headers: normHeaders
|
|
133
124
|
},
|
|
134
125
|
() => runWithCacheStore(() => runWithHtmlStore(async () => {
|
|
135
|
-
appHtml = await renderElementToHtml(
|
|
126
|
+
appHtml = await renderElementToHtml(element, ctx);
|
|
136
127
|
}))
|
|
137
128
|
);
|
|
138
|
-
const pageTitle = resolveTitle(store.titleOps,
|
|
129
|
+
const pageTitle = resolveTitle(store.titleOps, defaultTitle);
|
|
139
130
|
const headLines = [
|
|
140
131
|
' <meta charset="utf-8" />',
|
|
141
132
|
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
@@ -147,13 +138,13 @@ async function renderFile(filePath, params, url, pagesDir, isDev, res, req, stat
|
|
|
147
138
|
allIds: [...registry.keys()],
|
|
148
139
|
url,
|
|
149
140
|
params,
|
|
150
|
-
query
|
|
141
|
+
query,
|
|
151
142
|
headers: safeHeaders,
|
|
152
143
|
debug: toClientDebugLevel(getDebugLevel())
|
|
153
144
|
}).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
154
145
|
const bodyScriptLines = renderManagedBodyScripts(store);
|
|
155
146
|
const bodyScriptsHtml = bodyScriptLines.length > 0 ? "\n" + bodyScriptLines.join("\n") + "\n" : "";
|
|
156
|
-
|
|
147
|
+
return `<!DOCTYPE html>
|
|
157
148
|
${openTag("html", store.htmlAttrs)}
|
|
158
149
|
<head>
|
|
159
150
|
${headLines.join("\n")}
|
|
@@ -184,6 +175,38 @@ ${openTag("body", store.bodyAttrs)}
|
|
|
184
175
|
${isDev ? '<script type="module" src="/__hmr.js"></script>' : ""}
|
|
185
176
|
${bodyScriptsHtml}</body>
|
|
186
177
|
</html>`;
|
|
178
|
+
}
|
|
179
|
+
async function renderFile(filePath, params, url, pagesDir, isDev, res, req, statusCode, skipClientSSR) {
|
|
180
|
+
const searchParams = new URL(url, "http://localhost").searchParams;
|
|
181
|
+
const queryParams = {};
|
|
182
|
+
searchParams.forEach((_, k) => {
|
|
183
|
+
if (!(k in params)) {
|
|
184
|
+
const all = searchParams.getAll(k);
|
|
185
|
+
queryParams[k] = all.length > 1 ? all : all[0];
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
const mergedParams = { ...queryParams, ...params };
|
|
189
|
+
const layoutPaths = findLayoutsForRoute(filePath, pagesDir);
|
|
190
|
+
const { default: PageComponent } = await tsImport(
|
|
191
|
+
pathToFileURL(filePath).href,
|
|
192
|
+
{ parentURL: import.meta.url }
|
|
193
|
+
);
|
|
194
|
+
const wrappedElement = await wrapWithLayouts(
|
|
195
|
+
createElement(PageComponent, mergedParams),
|
|
196
|
+
layoutPaths
|
|
197
|
+
);
|
|
198
|
+
const html = await renderDocument({
|
|
199
|
+
registryEntryFiles: [filePath, ...layoutPaths],
|
|
200
|
+
pagesDir,
|
|
201
|
+
element: wrappedElement,
|
|
202
|
+
url,
|
|
203
|
+
params,
|
|
204
|
+
query: queryParams,
|
|
205
|
+
headers: req?.headers ?? {},
|
|
206
|
+
isDev,
|
|
207
|
+
skipClientSSR,
|
|
208
|
+
defaultTitle: "NukeJS"
|
|
209
|
+
});
|
|
187
210
|
res.statusCode = statusCode;
|
|
188
211
|
res.setHeader("Content-Type", "text/html");
|
|
189
212
|
res.end(html);
|
|
@@ -259,5 +282,6 @@ async function serverSideRender(url, res, pagesDir, isDev = false, req) {
|
|
|
259
282
|
}
|
|
260
283
|
}
|
|
261
284
|
export {
|
|
285
|
+
renderDocument,
|
|
262
286
|
serverSideRender
|
|
263
287
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukejs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
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",
|