nukejs 0.0.11 → 0.0.13

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.
Files changed (77) hide show
  1. package/README.md +147 -0
  2. package/dist/Link.js +0 -1
  3. package/dist/app.js +0 -1
  4. package/dist/build-common.js +64 -14
  5. package/dist/build-node.js +63 -5
  6. package/dist/build-vercel.js +76 -9
  7. package/dist/builder.js +32 -4
  8. package/dist/bundle.js +47 -4
  9. package/dist/bundler.js +0 -1
  10. package/dist/component-analyzer.js +0 -1
  11. package/dist/config.js +0 -1
  12. package/dist/hmr-bundle.js +10 -1
  13. package/dist/hmr.js +7 -1
  14. package/dist/html-store.js +0 -1
  15. package/dist/http-server.js +0 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.js +0 -1
  18. package/dist/logger.js +0 -1
  19. package/dist/metadata.js +0 -1
  20. package/dist/middleware-loader.js +0 -1
  21. package/dist/middleware.example.js +0 -1
  22. package/dist/middleware.js +0 -1
  23. package/dist/renderer.js +3 -9
  24. package/dist/request-store.js +0 -1
  25. package/dist/router.js +0 -1
  26. package/dist/ssr.js +73 -16
  27. package/dist/use-html.js +0 -1
  28. package/dist/use-request.js +0 -1
  29. package/dist/use-router.js +0 -1
  30. package/dist/utils.js +0 -1
  31. package/package.json +1 -1
  32. package/dist/Link.js.map +0 -7
  33. package/dist/app.d.ts +0 -19
  34. package/dist/app.js.map +0 -7
  35. package/dist/build-common.d.ts +0 -178
  36. package/dist/build-common.js.map +0 -7
  37. package/dist/build-node.d.ts +0 -15
  38. package/dist/build-node.js.map +0 -7
  39. package/dist/build-vercel.d.ts +0 -19
  40. package/dist/build-vercel.js.map +0 -7
  41. package/dist/builder.d.ts +0 -11
  42. package/dist/builder.js.map +0 -7
  43. package/dist/bundle.js.map +0 -7
  44. package/dist/bundler.d.ts +0 -58
  45. package/dist/bundler.js.map +0 -7
  46. package/dist/component-analyzer.d.ts +0 -75
  47. package/dist/component-analyzer.js.map +0 -7
  48. package/dist/config.d.ts +0 -35
  49. package/dist/config.js.map +0 -7
  50. package/dist/hmr-bundle.d.ts +0 -25
  51. package/dist/hmr-bundle.js.map +0 -7
  52. package/dist/hmr.d.ts +0 -55
  53. package/dist/hmr.js.map +0 -7
  54. package/dist/html-store.js.map +0 -7
  55. package/dist/http-server.d.ts +0 -92
  56. package/dist/http-server.js.map +0 -7
  57. package/dist/index.js.map +0 -7
  58. package/dist/logger.js.map +0 -7
  59. package/dist/metadata.d.ts +0 -51
  60. package/dist/metadata.js.map +0 -7
  61. package/dist/middleware-loader.d.ts +0 -50
  62. package/dist/middleware-loader.js.map +0 -7
  63. package/dist/middleware.d.ts +0 -22
  64. package/dist/middleware.example.d.ts +0 -8
  65. package/dist/middleware.example.js.map +0 -7
  66. package/dist/middleware.js.map +0 -7
  67. package/dist/renderer.d.ts +0 -44
  68. package/dist/renderer.js.map +0 -7
  69. package/dist/request-store.js.map +0 -7
  70. package/dist/router.d.ts +0 -92
  71. package/dist/router.js.map +0 -7
  72. package/dist/ssr.d.ts +0 -46
  73. package/dist/ssr.js.map +0 -7
  74. package/dist/use-html.js.map +0 -7
  75. package/dist/use-request.js.map +0 -7
  76. package/dist/use-router.js.map +0 -7
  77. package/dist/utils.js.map +0 -7
package/dist/bundle.js CHANGED
@@ -77,9 +77,38 @@ async function loadModules(ids, log, bust = "") {
77
77
  return mods;
78
78
  }
79
79
  const activeRoots = [];
80
+ let clientErrorPending = false;
81
+ function navigateToClientError(err) {
82
+ if (clientErrorPending) return;
83
+ if (window.location.search.includes("__clientError")) return;
84
+ clientErrorPending = true;
85
+ const message = err instanceof Error ? err.message : String(err);
86
+ const stack = err instanceof Error && err.stack ? err.stack : void 0;
87
+ const params = new URLSearchParams();
88
+ params.set("__clientError", message);
89
+ if (stack) params.set("__clientStack", stack);
90
+ window.dispatchEvent(new CustomEvent("locationchange", {
91
+ detail: { href: window.location.pathname + "?" + params.toString() }
92
+ }));
93
+ }
80
94
  async function mountNodes(mods, log) {
81
95
  const { hydrateRoot, createRoot } = await import("react-dom/client");
82
96
  const React = await import("react");
97
+ class NukeErrorBoundary extends React.default.Component {
98
+ constructor(props) {
99
+ super(props);
100
+ this.state = { caught: false };
101
+ }
102
+ static getDerivedStateFromError() {
103
+ return { caught: true };
104
+ }
105
+ componentDidCatch(error) {
106
+ navigateToClientError(error);
107
+ }
108
+ render() {
109
+ return this.state.caught ? null : this.props.children;
110
+ }
111
+ }
83
112
  const nodes = document.querySelectorAll("[data-hydrate-id]");
84
113
  log.verbose("Found", nodes.length, "hydration point(s)");
85
114
  for (const node of nodes) {
@@ -97,7 +126,8 @@ async function mountNodes(mods, log) {
97
126
  log.error("Props parse error for", id, e);
98
127
  }
99
128
  try {
100
- const element = React.default.createElement(Comp, await reconstructProps(rawProps, mods));
129
+ const inner = React.default.createElement(Comp, await reconstructProps(rawProps, mods));
130
+ const element = React.default.createElement(NukeErrorBoundary, null, inner);
101
131
  let root;
102
132
  if (node.innerHTML.trim()) {
103
133
  root = hydrateRoot(node, element);
@@ -223,8 +253,13 @@ function setupNavigation(log) {
223
253
  const fetchUrl = hmr ? href + (href.includes("?") ? "&" : "?") + "__hmr=1" : href;
224
254
  const response = await fetch(fetchUrl, { headers: { Accept: "text/html" } });
225
255
  if (!response.ok) {
226
- log.error("Navigation fetch failed:", response.status);
227
- return;
256
+ const ct = response.headers.get("content-type") ?? "";
257
+ if (!ct.includes("text/html")) {
258
+ log.error("Navigation fetch failed:", response.status, "\u2014 falling back to full reload");
259
+ window.location.href = href;
260
+ return;
261
+ }
262
+ log.info("Navigation returned", response.status, "\u2014 rendering error page in-place");
228
263
  }
229
264
  const parser = new DOMParser();
230
265
  const doc = parser.parseFromString(await response.text(), "text/html");
@@ -251,6 +286,8 @@ function setupNavigation(log) {
251
286
  } catch (err) {
252
287
  log.error("Navigation error, falling back to full reload:", err);
253
288
  window.location.href = href;
289
+ } finally {
290
+ clientErrorPending = false;
254
291
  }
255
292
  });
256
293
  }
@@ -258,6 +295,13 @@ async function initRuntime(data) {
258
295
  const log = makeLogger(data.debug ?? "silent");
259
296
  log.info("\u{1F680} Partial hydration:", data.hydrateIds.length, "root component(s)");
260
297
  setupNavigation(log);
298
+ window.onerror = (_msg, _src, _line, _col, err) => {
299
+ navigateToClientError(err ?? _msg);
300
+ return true;
301
+ };
302
+ window.onunhandledrejection = (e) => {
303
+ navigateToClientError(e.reason);
304
+ };
261
305
  const mods = await loadModules(data.allIds, log);
262
306
  await mountNodes(mods, log);
263
307
  log.info("\u{1F389} Done!");
@@ -267,4 +311,3 @@ export {
267
311
  initRuntime,
268
312
  setupLocationChangeMonitor
269
313
  };
270
- //# sourceMappingURL=bundle.js.map
package/dist/bundler.js CHANGED
@@ -97,4 +97,3 @@ export {
97
97
  serveNukeBundle,
98
98
  serveReactBundle
99
99
  };
100
- //# sourceMappingURL=bundler.js.map
@@ -123,4 +123,3 @@ export {
123
123
  getComponentCache,
124
124
  invalidateComponentCache
125
125
  };
126
- //# sourceMappingURL=component-analyzer.js.map
package/dist/config.js CHANGED
@@ -27,4 +27,3 @@ async function loadConfig() {
27
27
  export {
28
28
  loadConfig
29
29
  };
30
- //# sourceMappingURL=config.js.map
@@ -28,6 +28,16 @@ function hmr() {
28
28
  }
29
29
  return;
30
30
  }
31
+ if (msg.type === "layout-reload") {
32
+ const base = msg.base === "/" ? "" : msg.base;
33
+ const pathname = window.location.pathname;
34
+ const isUnder = pathname === (base || "/") || pathname.startsWith(base + "/");
35
+ if (isUnder) {
36
+ log.info("[HMR] Layout changed:", msg.base);
37
+ navigate(pathname + window.location.search);
38
+ }
39
+ return;
40
+ }
31
41
  if (msg.type === "replace") {
32
42
  log.info("[HMR] Component changed:", msg.component);
33
43
  navigate(window.location.pathname + window.location.search);
@@ -86,4 +96,3 @@ hmr();
86
96
  export {
87
97
  hmr as default
88
98
  };
89
- //# sourceMappingURL=hmr-bundle.js.map
package/dist/hmr.js CHANGED
@@ -24,7 +24,14 @@ function pageFileToUrl(filename) {
24
24
  function buildPayload(filename) {
25
25
  const normalized = filename.replace(/\\/g, "/");
26
26
  if (normalized.startsWith("pages/")) {
27
+ const stem = path.basename(normalized, path.extname(normalized));
28
+ if (stem === "_404" || stem === "_500") {
29
+ return { type: "replace", component: stem };
30
+ }
27
31
  const url = pageFileToUrl(normalized);
32
+ if (stem === "layout") {
33
+ return { type: "layout-reload", base: url };
34
+ }
28
35
  return { type: "reload", url };
29
36
  }
30
37
  const ext = path.extname(filename).toLowerCase();
@@ -59,4 +66,3 @@ export {
59
66
  hmrClients,
60
67
  watchDir
61
68
  };
62
- //# sourceMappingURL=hmr.js.map
@@ -39,4 +39,3 @@ export {
39
39
  resolveTitle,
40
40
  runWithHtmlStore
41
41
  };
42
- //# sourceMappingURL=html-store.js.map
@@ -170,4 +170,3 @@ export {
170
170
  parseBody,
171
171
  parseQuery
172
172
  };
173
- //# sourceMappingURL=http-server.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { useHtml } from './use-html';
2
- export type { HtmlOptions, TitleValue, HtmlAttrs, BodyAttrs, MetaTag, LinkTag, ScriptTag, StyleTag, } from './use-html';
2
+ export type { HtmlOptions } from './use-html';
3
+ export type { TitleValue, HtmlAttrs, BodyAttrs, MetaTag, LinkTag, ScriptTag, StyleTag, } from './html-store';
3
4
  export { default as useRouter } from './use-router';
4
5
  export { useRequest } from './use-request';
5
6
  export type { RequestContext } from './use-request';
package/dist/index.js CHANGED
@@ -22,4 +22,3 @@ export {
22
22
  useRequest,
23
23
  default2 as useRouter
24
24
  };
25
- //# sourceMappingURL=index.js.map
package/dist/logger.js CHANGED
@@ -50,4 +50,3 @@ export {
50
50
  log,
51
51
  setDebugLevel
52
52
  };
53
- //# sourceMappingURL=logger.js.map
package/dist/metadata.js CHANGED
@@ -40,4 +40,3 @@ export {
40
40
  renderScriptTag,
41
41
  renderStyleTag
42
42
  };
43
- //# sourceMappingURL=metadata.js.map
@@ -47,4 +47,3 @@ export {
47
47
  loadMiddleware,
48
48
  runMiddleware
49
49
  };
50
- //# sourceMappingURL=middleware-loader.js.map
@@ -55,4 +55,3 @@ function isRateLimited(ip) {
55
55
  export {
56
56
  middleware as default
57
57
  };
58
- //# sourceMappingURL=middleware.example.js.map
@@ -69,4 +69,3 @@ async function middleware(req, res) {
69
69
  export {
70
70
  middleware as default
71
71
  };
72
- //# sourceMappingURL=middleware.js.map
package/dist/renderer.js CHANGED
@@ -96,14 +96,9 @@ async function renderFunctionComponent(type, props, ctx) {
96
96
  return `<div style="color:red">Error rendering client component: ${escapeHtml(String(err))}</div>`;
97
97
  }
98
98
  }
99
- try {
100
- const result = type(props);
101
- const resolved = result?.then ? await result : result;
102
- return renderElementToHtml(resolved, ctx);
103
- } catch (err) {
104
- log.error("Error rendering component:", err);
105
- return `<div style="color:red">Error rendering component: ${escapeHtml(String(err))}</div>`;
106
- }
99
+ const result = type(props);
100
+ const resolved = result?.then ? await result : result;
101
+ return renderElementToHtml(resolved, ctx);
107
102
  }
108
103
  function serializePropsForHydration(props, registry) {
109
104
  if (!props || typeof props !== "object") return props;
@@ -153,4 +148,3 @@ function serializeReactElement(element, registry) {
153
148
  export {
154
149
  renderElementToHtml
155
150
  };
156
- //# sourceMappingURL=renderer.js.map
@@ -44,4 +44,3 @@ export {
44
44
  runWithRequestStore,
45
45
  sanitiseHeaders
46
46
  };
47
- //# sourceMappingURL=request-store.js.map
package/dist/router.js CHANGED
@@ -116,4 +116,3 @@ export {
116
116
  matchDynamicRoute,
117
117
  matchRoute
118
118
  };
119
- //# sourceMappingURL=router.js.map
package/dist/ssr.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import path from "path";
2
+ import fs from "fs";
2
3
  import { createElement } from "react";
3
4
  import { pathToFileURL } from "url";
4
5
  import { tsImport } from "tsx/esm/api";
@@ -90,18 +91,8 @@ function renderManagedBodyScripts(store) {
90
91
  if (bodyScripts.length === 0) return [];
91
92
  return [" <!--n-body-scripts-->", ...bodyScripts.map(renderScriptTag), " <!--/n-body-scripts-->"];
92
93
  }
93
- async function serverSideRender(url, res, pagesDir, isDev = false, req) {
94
- const skipClientSSR = url.includes("__hmr=1");
94
+ async function renderFile(filePath, params, url, pagesDir, isDev, res, req, statusCode, skipClientSSR) {
95
95
  const cleanUrl = url.split("?")[0];
96
- const routeMatch = matchRoute(cleanUrl, pagesDir);
97
- if (!routeMatch) {
98
- log.verbose(`No route found for: ${url}`);
99
- res.statusCode = 404;
100
- res.end("Page not found");
101
- return;
102
- }
103
- const { filePath, params, routePattern } = routeMatch;
104
- log.verbose(`SSR ${cleanUrl} -> ${path.relative(process.cwd(), filePath)}`);
105
96
  const searchParams = new URL(url, "http://localhost").searchParams;
106
97
  const queryParams = {};
107
98
  searchParams.forEach((_, k) => {
@@ -129,10 +120,6 @@ async function serverSideRender(url, res, pagesDir, isDev = false, req) {
129
120
  for (const layoutPath of layoutPaths)
130
121
  for (const [id, p] of findClientComponentsInTree(layoutPath, pagesDir))
131
122
  registry.set(id, p);
132
- log.verbose(
133
- `Page ${routePattern}: found ${registry.size} client component(s)`,
134
- `[${[...registry.keys()].join(", ")}]`
135
- );
136
123
  const ctx = { registry, hydrated: /* @__PURE__ */ new Set(), skipClientSSR };
137
124
  let appHtml = "";
138
125
  const store = await runWithRequestStore(
@@ -196,10 +183,80 @@ ${openTag("body", store.bodyAttrs)}
196
183
  ${isDev ? '<script type="module" src="/__hmr.js"></script>' : ""}
197
184
  ${bodyScriptsHtml}</body>
198
185
  </html>`;
186
+ res.statusCode = statusCode;
199
187
  res.setHeader("Content-Type", "text/html");
200
188
  res.end(html);
201
189
  }
190
+ function serializeError(err) {
191
+ if (err instanceof Error) {
192
+ const props = {
193
+ errorMessage: err.message
194
+ };
195
+ if (process.env.NODE_ENV !== "production" && err.stack)
196
+ props.errorStack = err.stack;
197
+ const status = err.status ?? err.statusCode;
198
+ if (status != null)
199
+ props.errorStatus = String(status);
200
+ return props;
201
+ }
202
+ return { errorMessage: String(err) };
203
+ }
204
+ async function tryRenderErrorPage(statusCode, pagesDir, res, isDev, req, error) {
205
+ const errorFile = path.join(pagesDir, `_${statusCode}.tsx`);
206
+ if (!fs.existsSync(errorFile)) return false;
207
+ const errorProps = error != null ? serializeError(error) : {};
208
+ try {
209
+ await renderFile(errorFile, errorProps, "/", pagesDir, isDev, res, req, statusCode, false);
210
+ return true;
211
+ } catch (err) {
212
+ log.error(`Error rendering _${statusCode}.tsx:`, err);
213
+ return false;
214
+ }
215
+ }
216
+ async function serverSideRender(url, res, pagesDir, isDev = false, req) {
217
+ const skipClientSSR = url.includes("__hmr=1");
218
+ const cleanUrl = url.split("?")[0];
219
+ const searchParams = new URL(url, "http://localhost").searchParams;
220
+ if (searchParams.has("__clientError")) {
221
+ const errorProps = {
222
+ errorMessage: searchParams.get("__clientError") ?? "Client error"
223
+ };
224
+ const stack = searchParams.get("__clientStack");
225
+ if (stack) errorProps.errorStack = stack;
226
+ const errorFile = path.join(pagesDir, "_500.tsx");
227
+ if (fs.existsSync(errorFile)) {
228
+ try {
229
+ await renderFile(errorFile, errorProps, url, pagesDir, isDev, res, req, 500, false);
230
+ } catch (err) {
231
+ log.error("Error rendering _500.tsx for client error:", err);
232
+ res.statusCode = 500;
233
+ res.end("Internal Server Error");
234
+ }
235
+ } else {
236
+ res.statusCode = 500;
237
+ res.end("Internal Server Error");
238
+ }
239
+ return;
240
+ }
241
+ const routeMatch = matchRoute(cleanUrl, pagesDir);
242
+ if (!routeMatch) {
243
+ log.verbose(`No route found for: ${url}`);
244
+ if (await tryRenderErrorPage(404, pagesDir, res, isDev, req)) return;
245
+ res.statusCode = 404;
246
+ res.end("Page not found");
247
+ return;
248
+ }
249
+ const { filePath, params, routePattern } = routeMatch;
250
+ log.verbose(`SSR ${cleanUrl} -> ${path.relative(process.cwd(), filePath)}`);
251
+ try {
252
+ await renderFile(filePath, params, url, pagesDir, isDev, res, req, 200, skipClientSSR);
253
+ } catch (err) {
254
+ log.error("SSR render error:", err);
255
+ if (await tryRenderErrorPage(500, pagesDir, res, isDev, req, err)) return;
256
+ res.statusCode = 500;
257
+ res.end("Internal Server Error");
258
+ }
259
+ }
202
260
  export {
203
261
  serverSideRender
204
262
  };
205
- //# sourceMappingURL=ssr.js.map
package/dist/use-html.js CHANGED
@@ -126,4 +126,3 @@ function domAttr(key) {
126
126
  export {
127
127
  useHtml
128
128
  };
129
- //# sourceMappingURL=use-html.js.map
@@ -46,4 +46,3 @@ function useRequest() {
46
46
  export {
47
47
  useRequest
48
48
  };
49
- //# sourceMappingURL=use-request.js.map
@@ -25,4 +25,3 @@ function useRouter() {
25
25
  export {
26
26
  useRouter as default
27
27
  };
28
- //# sourceMappingURL=use-router.js.map
package/dist/utils.js CHANGED
@@ -59,4 +59,3 @@ export {
59
59
  escapeHtml,
60
60
  getMimeType
61
61
  };
62
- //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukejs",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
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",
package/dist/Link.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/Link.tsx"],
4
- "sourcesContent": ["\"use client\";\r\nimport useRouter from \"./use-router\";\r\n\r\ninterface LinkProps {\r\n href: string;\r\n children: React.ReactNode;\r\n className?: string;\r\n}\r\n\r\n/**\r\n * Client-side navigation link.\r\n * Intercepts clicks and delegates to useRouter().push() so the SPA router\r\n * handles the transition without a full page reload.\r\n */\r\nconst Link = ({ href, children, className }: LinkProps) => {\r\n const { push } = useRouter();\r\n\r\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n e.preventDefault();\r\n push(href);\r\n };\r\n\r\n return (\r\n <a href={href} onClick={handleClick} className={className}>\r\n {children}\r\n </a>\r\n );\r\n};\r\n\r\nexport default Link;"],
5
- "mappings": ";AAuBI;AAtBJ,OAAO,eAAe;AAatB,MAAM,OAAO,CAAC,EAAE,MAAM,UAAU,UAAU,MAAiB;AACzD,QAAM,EAAE,KAAK,IAAI,UAAU;AAE3B,QAAM,cAAc,CAAC,MAA2C;AAC9D,MAAE,eAAe;AACjB,SAAK,IAAI;AAAA,EACX;AAEA,SACE,oBAAC,OAAE,MAAY,SAAS,aAAa,WAClC,UACH;AAEJ;AAEA,IAAO,eAAQ;",
6
- "names": []
7
- }
package/dist/app.d.ts DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * app.ts — NukeJS Dev Server Entry Point
3
- *
4
- * This is the runtime that powers `nuke dev`. It:
5
- * 1. Loads your nuke.config.ts (or uses sensible defaults)
6
- * 2. Discovers API route prefixes from your server directory
7
- * 3. Starts an HTTP server that handles:
8
- * app/public/** — static files (highest priority, via middleware)
9
- * /__hmr_ping — heartbeat for HMR reconnect polling
10
- * /__react.js — bundled React + ReactDOM (resolved via importmap)
11
- * /__n.js — NukeJS client runtime bundle
12
- * /__client-component/* — on-demand "use client" component bundles
13
- * server/** — API route handlers from serverDir
14
- * /** — SSR pages from app/pages (lowest priority)
15
- * 4. Watches for file changes and broadcasts HMR events to connected browsers
16
- *
17
- * In production (ENVIRONMENT=production), HMR and all file watching are skipped.
18
- */
19
- export {};
package/dist/app.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/app.ts"],
4
- "sourcesContent": ["/**\r\n * app.ts \u2014 NukeJS Dev Server Entry Point\r\n *\r\n * This is the runtime that powers `nuke dev`. It:\r\n * 1. Loads your nuke.config.ts (or uses sensible defaults)\r\n * 2. Discovers API route prefixes from your server directory\r\n * 3. Starts an HTTP server that handles:\r\n * app/public/** \u2014 static files (highest priority, via middleware)\r\n * /__hmr_ping \u2014 heartbeat for HMR reconnect polling\r\n * /__react.js \u2014 bundled React + ReactDOM (resolved via importmap)\r\n * /__n.js \u2014 NukeJS client runtime bundle\r\n * /__client-component/* \u2014 on-demand \"use client\" component bundles\r\n * server/** \u2014 API route handlers from serverDir\r\n * /** \u2014 SSR pages from app/pages (lowest priority)\r\n * 4. Watches for file changes and broadcasts HMR events to connected browsers\r\n *\r\n * In production (ENVIRONMENT=production), HMR and all file watching are skipped.\r\n */\r\n\r\nimport http from 'http';\r\nimport path from 'path';\r\nimport { existsSync, watch } from 'fs';\r\n\r\nimport { ansi, c, log, setDebugLevel, getDebugLevel } from './logger';\r\nimport { loadConfig } from './config';\r\nimport { discoverApiPrefixes, matchApiPrefix, createApiHandler } from './http-server';\r\nimport { loadMiddleware, runMiddleware } from './middleware-loader';\r\nimport { serveReactBundle, serveNukeBundle, serveClientComponentBundle } from './bundler';\r\nimport { serverSideRender } from './ssr';\r\nimport { watchDir, broadcastRestart } from './hmr';\r\n\r\n// \u2500\u2500\u2500 Environment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst isDev = process.env.ENVIRONMENT !== 'production';\r\n\r\n// React must live on globalThis so dynamically-imported page modules can share\r\n// the same React instance without each bundling their own copy.\r\nif (isDev) {\r\n const React = await import('react');\r\n (global as any).React = React;\r\n}\r\n\r\n// \u2500\u2500\u2500 Config & paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst config = await loadConfig();\r\nsetDebugLevel(config.debug ?? false);\r\n\r\nconst PAGES_DIR = path.resolve('./app/pages');\r\nconst SERVER_DIR = path.resolve(config.serverDir);\r\nconst PORT = config.port;\r\n\r\nlog.info('Configuration loaded:');\r\nlog.info(` - Pages directory: ${PAGES_DIR}`);\r\nlog.info(` - Server directory: ${SERVER_DIR}`);\r\nlog.info(` - Port: ${PORT}`);\r\nlog.info(` - Debug level: ${String(getDebugLevel())}`);\r\nlog.info(` - Dev mode: ${String(isDev)}`);\r\n\r\n// \u2500\u2500\u2500 API route discovery \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n// Start watching the app directory for HMR.\r\nif (isDev) watchDir(path.resolve('./app'), 'App');\r\n\r\n// apiPrefixes is a live, mutable array. In dev, we splice it in-place whenever\r\n// the server directory changes so handlers always see the latest routes without\r\n// a full restart.\r\nconst apiPrefixes = discoverApiPrefixes(SERVER_DIR);\r\nconst handleApiRoute = createApiHandler({ apiPrefixes, port: PORT, isDev });\r\n\r\nlog.info(`API prefixes discovered: ${apiPrefixes.length === 0 ? 'none' : ''}`);\r\napiPrefixes.forEach(p => {\r\n log.info(` - ${p.prefix || '/'} -> ${path.relative(process.cwd(), p.directory)}`);\r\n});\r\n\r\n// \u2500\u2500\u2500 Full-restart file watchers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n// Some changes can't be hot-patched: middleware exports change the request\r\n// pipeline, and nuke.config.ts may change the port or serverDir. On change we\r\n// broadcast a 'restart' SSE event so browsers reconnect automatically, then\r\n// exit with code 75 \u2014 the CLI watches for this to respawn the process.\r\nif (isDev) {\r\n const RESTART_EXIT_CODE = 75;\r\n const restartFiles = [\r\n path.resolve('./middleware.ts'),\r\n path.resolve('./nuke.config.ts'),\r\n ];\r\n\r\n for (const filePath of restartFiles) {\r\n if (!existsSync(filePath)) continue;\r\n watch(filePath, async () => {\r\n log.info(`[Server] ${path.basename(filePath)} changed \u2014 restarting...`);\r\n await broadcastRestart();\r\n process.exit(RESTART_EXIT_CODE);\r\n });\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Middleware \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n// Loads built-in middleware (HMR SSE/JS endpoints) and the user-supplied\r\n// middleware.ts from the project root (if it exists).\r\nawait loadMiddleware();\r\n\r\n// \u2500\u2500\u2500 Request handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst server = http.createServer(async (req, res) => {\r\n try {\r\n // Middleware runs first. If it calls res.end() the request is fully\r\n // handled and we bail out immediately.\r\n const middlewareHandled = await runMiddleware(req, res);\r\n if (middlewareHandled) return;\r\n\r\n const url = req.url || '/';\r\n\r\n // \u2500\u2500 Internal NukeJS routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Framework files are checked before server routes so a user route can\r\n // never accidentally shadow /__n.js, /__react.js, etc.\r\n\r\n // Heartbeat polled by the HMR client to know when the server is back up.\r\n if (url === '/__hmr_ping') {\r\n res.setHeader('Content-Type', 'text/plain');\r\n res.end('ok');\r\n return;\r\n }\r\n\r\n // Unified React bundle (react + react-dom/client + react/jsx-runtime).\r\n // Resolved by the importmap injected into every SSR page, so client\r\n // components never bundle React themselves.\r\n if (url === '/__react.js')\r\n return await serveReactBundle(res);\r\n\r\n // NukeJS browser runtime: initRuntime, SPA navigation, partial hydration.\r\n if (url === '/__n.js')\r\n return await serveNukeBundle(res);\r\n\r\n // On-demand bundles for individual \"use client\" components.\r\n // Strip the prefix, the .js extension, and any query string (cache buster).\r\n if (url.startsWith('/__client-component/'))\r\n return await serveClientComponentBundle(\r\n url.slice(20).split('?')[0].replace('.js', ''),\r\n res,\r\n );\r\n\r\n // \u2500\u2500 Server routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // API routes from serverDir \u2014 checked after framework files, before pages.\r\n if (matchApiPrefix(url, apiPrefixes))\r\n return await handleApiRoute(url, req, res);\r\n\r\n // \u2500\u2500 Page SSR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Nothing above matched \u2014 render a page from app/pages.\r\n return await serverSideRender(url, res, PAGES_DIR, isDev, req);\r\n\r\n } catch (error) {\r\n log.error('Server error:', error);\r\n res.statusCode = 500;\r\n res.end('Internal server error');\r\n }\r\n});\r\n\r\n// \u2500\u2500\u2500 Port binding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Tries to listen on `port`. If the port is already in use (EADDRINUSE),\r\n * increments and tries the next port until one is free.\r\n *\r\n * Returns the port that was actually bound.\r\n */\r\nfunction tryListen(port: number): Promise<number> {\r\n return new Promise((resolve, reject) => {\r\n server.once('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EADDRINUSE') resolve(tryListen(port + 1));\r\n else reject(err);\r\n });\r\n server.listen(port, () => resolve(port));\r\n });\r\n}\r\n\r\n// \u2500\u2500\u2500 Startup banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Renders the \u2622\uFE0F NukeJS startup box to stdout.\r\n * Uses box-drawing characters and ANSI colour codes for a clean terminal UI.\r\n */\r\nfunction printStartupBanner(port: number, isDev: boolean): void {\r\n const url = `http://localhost:${port}`;\r\n const level = getDebugLevel();\r\n const debugStr = String(level);\r\n const innerWidth = 42;\r\n const line = '\u2500'.repeat(innerWidth);\r\n\r\n /** Right-pads `text` to `width` columns, ignoring invisible ANSI sequences. */\r\n const pad = (text: string, width: number) => {\r\n const visibleLen = text.replace(/\\x1b\\[[0-9;]*m/g, '').length;\r\n return text + ' '.repeat(Math.max(0, width - visibleLen));\r\n };\r\n\r\n const row = (content: string, w = 2) =>\r\n `${ansi.gray}\u2502${ansi.reset} ${pad(content, innerWidth - w)} ${ansi.gray}\u2502${ansi.reset}`;\r\n const label = (key: string, val: string) =>\r\n row(`${c('gray', key)} ${val}`);\r\n\r\n console.log('');\r\n console.log(`${ansi.gray}\u250C${line}\u2510${ansi.reset}`);\r\n console.log(row(` ${c('red', '\u2622\uFE0F nukejs ', true)}`, 1));\r\n console.log(`${ansi.gray}\u251C${line}\u2524${ansi.reset}`);\r\n console.log(label(' Local ', c('cyan', url, true)));\r\n console.log(`${ansi.gray}\u251C${line}\u2524${ansi.reset}`);\r\n console.log(label(' Pages ', c('white', path.relative(process.cwd(), PAGES_DIR))));\r\n console.log(label(' Server ', c('white', path.relative(process.cwd(), SERVER_DIR))));\r\n console.log(label(' Dev ', isDev ? c('green', 'yes') : c('gray', 'no')));\r\n console.log(label(' Debug ', level === false\r\n ? c('gray', 'off')\r\n : level === true\r\n ? c('green', 'verbose')\r\n : c('yellow', debugStr)));\r\n console.log(`${ansi.gray}\u2514${line}\u2518${ansi.reset}`);\r\n console.log('');\r\n}\r\n\r\nconst actualPort = await tryListen(PORT);\r\nprintStartupBanner(actualPort, isDev);"],
5
- "mappings": "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",
6
- "names": ["isDev"]
7
- }
@@ -1,178 +0,0 @@
1
- /**
2
- * build-common.ts — Shared Build Logic
3
- *
4
- * Used by both build-node.ts and build-vercel.ts.
5
- *
6
- * Exports:
7
- * — types : AnalyzedRoute, ServerPage, BuiltPage,
8
- * PageAdapterOptions, PageBundleOptions
9
- * — utility helpers : walkFiles, analyzeFile, isServerComponent,
10
- * findPageLayouts, extractDefaultExportName
11
- * — collection : collectServerPages, collectGlobalClientRegistry,
12
- * buildPerPageRegistry
13
- * — template codegen : makeApiAdapterSource, makePageAdapterSource
14
- * — bundle ops : bundleApiHandler, bundlePageHandler,
15
- * bundleClientComponents, buildPages,
16
- * buildCombinedBundle, copyPublicFiles
17
- */
18
- export interface AnalyzedRoute {
19
- /** Regex string matching the URL path, e.g. '^/users/([^/]+)$' */
20
- srcRegex: string;
21
- /** Names of captured groups in srcRegex order */
22
- paramNames: string[];
23
- /**
24
- * Subset of paramNames that are catch-all ([...slug] or [[...path]]).
25
- * Their runtime values are string[] not string.
26
- */
27
- catchAllNames: string[];
28
- /** Function namespace path, e.g. '/api/users' or '/page/about' */
29
- funcPath: string;
30
- specificity: number;
31
- }
32
- export interface ServerPage extends AnalyzedRoute {
33
- absPath: string;
34
- }
35
- export interface BuiltPage extends ServerPage {
36
- bundleText: string;
37
- }
38
- export declare function walkFiles(dir: string, base?: string): string[];
39
- /**
40
- * Parses dynamic-route segments from a relative file path and returns a regex,
41
- * captured param names, catch-all param names, a function path, and a
42
- * specificity score.
43
- *
44
- * Supported patterns per segment:
45
- * [[...name]] optional catch-all → regex (.*) → string[]
46
- * [...name] required catch-all → regex (.+) → string[]
47
- * [[name]] optional single → regex ([^/]*)? → string
48
- * [name] required single → regex ([^/]+) → string
49
- * literal static → escaped literal
50
- *
51
- * @param relPath Relative path from the dir root (e.g. 'users/[id].tsx').
52
- * @param prefix Namespace for funcPath ('api' | 'page').
53
- */
54
- export declare function analyzeFile(relPath: string, prefix?: string): AnalyzedRoute;
55
- /**
56
- * Returns true when a file does NOT begin with a "use client" directive,
57
- * i.e. it is a server component.
58
- */
59
- export declare function isServerComponent(filePath: string): boolean;
60
- /**
61
- * Walks from the pages root to the directory containing `routeFilePath` and
62
- * returns every layout.tsx found, in outermost-first order.
63
- */
64
- export declare function findPageLayouts(routeFilePath: string, pagesDir: string): string[];
65
- /**
66
- * Extracts the identifier used as the default export from a component file.
67
- * Returns null when no default export is found.
68
- *
69
- * Handles three formats so that components compiled by esbuild are recognised
70
- * alongside hand-written source files:
71
- * 1. Source: `export default function Foo` / `export default Foo`
72
- * 2. esbuild: `var Foo_default = Foo` (compiled arrow-function component)
73
- * 3. Re-export: `export { Foo as default }`
74
- */
75
- export declare function extractDefaultExportName(filePath: string): string | null;
76
- /**
77
- * Returns all server-component pages inside `pagesDir`, sorted most-specific
78
- * first so precise routes shadow catch-alls in routers.
79
- * layout.tsx files and "use client" files are excluded.
80
- */
81
- export declare function collectServerPages(pagesDir: string): ServerPage[];
82
- /**
83
- * Walks every server page and its layout chain to collect all client component
84
- * IDs reachable anywhere in the app.
85
- */
86
- export declare function collectGlobalClientRegistry(serverPages: ServerPage[], pagesDir: string): Map<string, string>;
87
- /**
88
- * Builds the per-page client component registry (page + its layout chain)
89
- * and returns both the id→path map and the name→id map needed by
90
- * bundlePageHandler.
91
- */
92
- export declare function buildPerPageRegistry(absPath: string, layoutPaths: string[], pagesDir: string): {
93
- registry: Map<string, string>;
94
- clientComponentNames: Record<string, string>;
95
- };
96
- /**
97
- * Runs both passes of the page build:
98
- *
99
- * Pass 1 — bundles all client components to `staticDir/__client-component/`
100
- * and collects pre-rendered HTML for each.
101
- * Pass 2 — bundles every server-component page into a self-contained ESM
102
- * handler and returns the results as `BuiltPage[]`.
103
- */
104
- export declare function buildPages(pagesDir: string, staticDir: string): Promise<BuiltPage[]>;
105
- /**
106
- * Returns the TypeScript source for a thin HTTP adapter that wraps an API
107
- * route module and exposes a single `handler(req, res)` default export.
108
- */
109
- export declare function makeApiAdapterSource(handlerFilename: string): string;
110
- export interface PageAdapterOptions {
111
- /** e.g. './home.tsx' — relative import for the page default export */
112
- pageImport: string;
113
- /** Newline-joined import statements for layout components */
114
- layoutImports: string;
115
- /** function-name → cc_id map, computed at build time */
116
- clientComponentNames: Record<string, string>;
117
- /** All client component IDs reachable from this page */
118
- allClientIds: string[];
119
- /** Comma-separated list of __layout_N__ identifiers */
120
- layoutArrayItems: string;
121
- /** Pre-rendered HTML per client component ID, computed at build time */
122
- prerenderedHtml: Record<string, string>;
123
- /**
124
- * All dynamic route param names for this page (e.g. ['id', 'slug']).
125
- * Used to distinguish route segments from real query-string params at runtime.
126
- */
127
- routeParamNames: string[];
128
- /** Subset of routeParamNames whose values are string[] (catch-all segments) */
129
- catchAllNames: string[];
130
- }
131
- /**
132
- * Returns the TypeScript source for a fully self-contained page handler.
133
- *
134
- * The adapter:
135
- * • Inlines the html-store so useHtml() works without external deps.
136
- * • Contains an async recursive renderer for server + client components.
137
- * • Client components are identified via the pre-computed CLIENT_COMPONENTS
138
- * map — no fs.readFileSync at runtime.
139
- * • Emits the full HTML document including the __n_data blob and bootstrap.
140
- */
141
- export declare function makePageAdapterSource(opts: PageAdapterOptions): string;
142
- /**
143
- * Bundles an API route handler into a single self-contained ESM string.
144
- * node_modules are kept external — they exist at runtime on both Node and
145
- * Vercel (Vercel bundles them separately via the pages dispatcher).
146
- */
147
- export declare function bundleApiHandler(absPath: string): Promise<string>;
148
- export interface PageBundleOptions {
149
- absPath: string;
150
- pagesDir: string;
151
- clientComponentNames: Record<string, string>;
152
- allClientIds: string[];
153
- layoutPaths: string[];
154
- prerenderedHtml: Record<string, string>;
155
- routeParamNames: string[];
156
- catchAllNames: string[];
157
- }
158
- /**
159
- * Bundles a server-component page into a single self-contained ESM string.
160
- * All npm packages are kept external — the Node production server has
161
- * node_modules available at runtime.
162
- */
163
- export declare function bundlePageHandler(opts: PageBundleOptions): Promise<string>;
164
- /**
165
- * Bundles every client component in `globalRegistry` to
166
- * `<staticDir>/__client-component/<id>.js` and pre-renders each to HTML.
167
- */
168
- export declare function bundleClientComponents(globalRegistry: Map<string, string>, pagesDir: string, staticDir: string): Promise<Map<string, string>>;
169
- /**
170
- * Builds the combined browser bundle (__n.js) that contains the full React
171
- * runtime + NukeJS client runtime in a single file.
172
- */
173
- export declare function buildCombinedBundle(staticDir: string): Promise<void>;
174
- /**
175
- * Recursively copies every file from `publicDir` into `destDir`, preserving
176
- * the directory structure. Skips silently when `publicDir` does not exist.
177
- */
178
- export declare function copyPublicFiles(publicDir: string, destDir: string): void;