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 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 renderFile(filePath, params, url, pagesDir, isDev, res, req, statusCode, skipClientSSR) {
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 searchParams = new URL(url, "http://localhost").searchParams;
98
- const queryParams = {};
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 [id, p] of findClientComponentsInTree(filePath, pagesDir))
120
- registry.set(id, p);
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: queryParams,
122
+ query,
132
123
  headers: normHeaders
133
124
  },
134
125
  () => runWithCacheStore(() => runWithHtmlStore(async () => {
135
- appHtml = await renderElementToHtml(wrappedElement, ctx);
126
+ appHtml = await renderElementToHtml(element, ctx);
136
127
  }))
137
128
  );
138
- const pageTitle = resolveTitle(store.titleOps, "NukeJS");
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: queryParams,
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
- const html = `<!DOCTYPE html>
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.29",
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",