nukejs 0.0.1 → 0.0.3

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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +529 -0
  3. package/bin/index.mjs +126 -0
  4. package/dist/app.d.ts +18 -0
  5. package/dist/app.js +124 -0
  6. package/dist/app.js.map +7 -0
  7. package/dist/as-is/Link.d.ts +6 -0
  8. package/dist/as-is/Link.tsx +20 -0
  9. package/dist/as-is/useRouter.d.ts +7 -0
  10. package/dist/as-is/useRouter.ts +33 -0
  11. package/dist/build-common.d.ts +192 -0
  12. package/dist/build-common.js +737 -0
  13. package/dist/build-common.js.map +7 -0
  14. package/dist/build-node.d.ts +1 -0
  15. package/dist/build-node.js +170 -0
  16. package/dist/build-node.js.map +7 -0
  17. package/dist/build-vercel.d.ts +1 -0
  18. package/dist/build-vercel.js +65 -0
  19. package/dist/build-vercel.js.map +7 -0
  20. package/dist/builder.d.ts +1 -0
  21. package/dist/builder.js +97 -0
  22. package/dist/builder.js.map +7 -0
  23. package/dist/bundle.d.ts +68 -0
  24. package/dist/bundle.js +166 -0
  25. package/dist/bundle.js.map +7 -0
  26. package/dist/bundler.d.ts +58 -0
  27. package/dist/bundler.js +100 -0
  28. package/dist/bundler.js.map +7 -0
  29. package/dist/component-analyzer.d.ts +72 -0
  30. package/dist/component-analyzer.js +102 -0
  31. package/dist/component-analyzer.js.map +7 -0
  32. package/dist/config.d.ts +35 -0
  33. package/dist/config.js +30 -0
  34. package/dist/config.js.map +7 -0
  35. package/dist/hmr-bundle.d.ts +25 -0
  36. package/dist/hmr-bundle.js +76 -0
  37. package/dist/hmr-bundle.js.map +7 -0
  38. package/dist/hmr.d.ts +55 -0
  39. package/dist/hmr.js +62 -0
  40. package/dist/hmr.js.map +7 -0
  41. package/dist/html-store.d.ts +121 -0
  42. package/dist/html-store.js +42 -0
  43. package/dist/html-store.js.map +7 -0
  44. package/dist/http-server.d.ts +99 -0
  45. package/dist/http-server.js +166 -0
  46. package/dist/http-server.js.map +7 -0
  47. package/dist/index.d.ts +9 -0
  48. package/dist/index.js +20 -0
  49. package/dist/index.js.map +7 -0
  50. package/dist/logger.d.ts +58 -0
  51. package/dist/logger.js +53 -0
  52. package/dist/logger.js.map +7 -0
  53. package/dist/metadata.d.ts +50 -0
  54. package/dist/metadata.js +43 -0
  55. package/dist/metadata.js.map +7 -0
  56. package/dist/middleware-loader.d.ts +50 -0
  57. package/dist/middleware-loader.js +50 -0
  58. package/dist/middleware-loader.js.map +7 -0
  59. package/dist/middleware.d.ts +22 -0
  60. package/dist/middleware.example.d.ts +8 -0
  61. package/dist/middleware.example.js +58 -0
  62. package/dist/middleware.example.js.map +7 -0
  63. package/dist/middleware.js +72 -0
  64. package/dist/middleware.js.map +7 -0
  65. package/dist/renderer.d.ts +44 -0
  66. package/dist/renderer.js +130 -0
  67. package/dist/renderer.js.map +7 -0
  68. package/dist/router.d.ts +84 -0
  69. package/dist/router.js +104 -0
  70. package/dist/router.js.map +7 -0
  71. package/dist/ssr.d.ts +39 -0
  72. package/dist/ssr.js +168 -0
  73. package/dist/ssr.js.map +7 -0
  74. package/dist/use-html.d.ts +64 -0
  75. package/dist/use-html.js +125 -0
  76. package/dist/use-html.js.map +7 -0
  77. package/dist/utils.d.ts +26 -0
  78. package/dist/utils.js +62 -0
  79. package/dist/utils.js.map +7 -0
  80. package/package.json +63 -12
@@ -0,0 +1,68 @@
1
+ /**
2
+ * bundle.ts — NukeJS Client Runtime
3
+ *
4
+ * This file is compiled by esbuild into /__n.js and served to every page.
5
+ * It provides:
6
+ *
7
+ * initRuntime(data) — called once per page load to hydrate
8
+ * "use client" components and wire up SPA nav
9
+ * setupLocationChangeMonitor() — patches history.pushState/replaceState so
10
+ * SPA navigation fires a 'locationchange' event
11
+ *
12
+ * Hydration model (partial hydration):
13
+ * - The server renders the full page to HTML, wrapping each client component
14
+ * in a <span data-hydrate-id="cc_…" data-hydrate-props="…"> marker.
15
+ * - initRuntime loads the matching JS bundle for each marker and calls
16
+ * hydrateRoot() on it, letting React take over just that subtree.
17
+ * - Props serialized by the server may include nested React elements
18
+ * (serialized as { __re: 'html'|'client', … }), which are reconstructed
19
+ * back into React.createElement calls before mounting.
20
+ *
21
+ * SPA navigation:
22
+ * - Link clicks / programmatic navigation dispatch a 'locationchange' event.
23
+ * - The handler fetches the target URL as HTML, diffs the #app container,
24
+ * unmounts the old React roots, and re-hydrates the new ones.
25
+ * - HMR navigations add ?__hmr=1 so the server skips client-SSR (faster).
26
+ */
27
+ /**
28
+ * Patches history.pushState and history.replaceState to fire a custom
29
+ * 'locationchange' event on window. Also listens to 'popstate' for
30
+ * back/forward navigation.
31
+ *
32
+ * This must be called after initRuntime sets up the navigation listener so
33
+ * there's no race between the event firing and the listener being registered.
34
+ */
35
+ export declare function setupLocationChangeMonitor(): void;
36
+ type ClientDebugLevel = 'silent' | 'error' | 'info' | 'verbose';
37
+ /** Shape of the JSON blob embedded as #__n_data in every SSR page. */
38
+ export interface RuntimeData {
39
+ /** IDs of client components actually rendered on this page (subset of allIds). */
40
+ hydrateIds: string[];
41
+ /** All client component IDs reachable from this page, including layouts.
42
+ * Pre-loaded so SPA navigations to related pages feel instant. */
43
+ allIds: string[];
44
+ url: string;
45
+ params: Record<string, any>;
46
+ debug: ClientDebugLevel;
47
+ }
48
+ /**
49
+ * Bootstraps the NukeJS client runtime.
50
+ *
51
+ * Called once per page load from the inline <script type="module"> injected
52
+ * by the SSR renderer:
53
+ *
54
+ * ```js
55
+ * const { initRuntime } = await import('nukejs');
56
+ * const data = JSON.parse(document.getElementById('__n_data').textContent);
57
+ * await initRuntime(data);
58
+ * ```
59
+ *
60
+ * Order of operations:
61
+ * 1. Create the logger at the configured debug level.
62
+ * 2. Wire up SPA navigation listener.
63
+ * 3. Load all client component bundles in parallel.
64
+ * 4. Hydrate every [data-hydrate-id] node.
65
+ * 5. Patch history.pushState/replaceState so Link clicks trigger navigation.
66
+ */
67
+ export declare function initRuntime(data: RuntimeData): Promise<void>;
68
+ export {};
package/dist/bundle.js ADDED
@@ -0,0 +1,166 @@
1
+ function setupLocationChangeMonitor() {
2
+ const originalPushState = window.history.pushState.bind(window.history);
3
+ const originalReplaceState = window.history.replaceState.bind(window.history);
4
+ const dispatch = (href) => window.dispatchEvent(new CustomEvent("locationchange", { detail: { href } }));
5
+ window.history.pushState = function(...args) {
6
+ originalPushState(...args);
7
+ dispatch(args[2]);
8
+ };
9
+ window.history.replaceState = function(...args) {
10
+ originalReplaceState(...args);
11
+ dispatch(args[2]);
12
+ };
13
+ window.addEventListener("popstate", () => dispatch(window.location.pathname));
14
+ }
15
+ function makeLogger(level) {
16
+ return {
17
+ verbose: (...a) => {
18
+ if (level === "verbose") console.log(...a);
19
+ },
20
+ info: (...a) => {
21
+ if (level === "verbose" || level === "info") console.log(...a);
22
+ },
23
+ warn: (...a) => {
24
+ if (level === "verbose" || level === "info") console.warn(...a);
25
+ },
26
+ error: (...a) => {
27
+ if (level !== "silent") console.error(...a);
28
+ }
29
+ };
30
+ }
31
+ async function reconstructElement(node, mods) {
32
+ if (node === null || node === void 0) return node;
33
+ if (typeof node !== "object") return node;
34
+ if (Array.isArray(node)) {
35
+ const items = await Promise.all(node.map((n) => reconstructElement(n, mods)));
36
+ const React = await import("react");
37
+ return items.map(
38
+ (el, i) => el && typeof el === "object" && el.$$typeof ? React.default.cloneElement(el, { key: el.key ?? i }) : el
39
+ );
40
+ }
41
+ if (node.__re === "client") {
42
+ const n = node;
43
+ const Comp = mods.get(n.componentId);
44
+ if (!Comp) return null;
45
+ const React = await import("react");
46
+ return React.default.createElement(Comp, await reconstructProps(n.props, mods));
47
+ }
48
+ if (node.__re === "html") {
49
+ const n = node;
50
+ const React = await import("react");
51
+ return React.default.createElement(n.tag, await reconstructProps(n.props, mods));
52
+ }
53
+ return node;
54
+ }
55
+ async function reconstructProps(props, mods) {
56
+ if (!props || typeof props !== "object" || Array.isArray(props))
57
+ return reconstructElement(props, mods);
58
+ const out = {};
59
+ for (const [k, v] of Object.entries(props))
60
+ out[k] = await reconstructElement(v, mods);
61
+ return out;
62
+ }
63
+ async function loadModules(ids, log, bust = "") {
64
+ const mods = /* @__PURE__ */ new Map();
65
+ await Promise.all(
66
+ ids.map(async (id) => {
67
+ try {
68
+ const url = `/__client-component/${id}.js` + (bust ? `?t=${bust}` : "");
69
+ const m = await import(url);
70
+ mods.set(id, m.default);
71
+ log.verbose("\u2713 Loaded:", id);
72
+ } catch (err) {
73
+ log.error("\u2717 Load failed:", id, err);
74
+ }
75
+ })
76
+ );
77
+ return mods;
78
+ }
79
+ const activeRoots = [];
80
+ async function mountNodes(mods, log, isNavigation) {
81
+ const { hydrateRoot, createRoot } = await import("react-dom/client");
82
+ const React = await import("react");
83
+ const nodes = document.querySelectorAll("[data-hydrate-id]");
84
+ log.verbose("Found", nodes.length, "hydration point(s)");
85
+ for (const node of nodes) {
86
+ if (node.parentElement?.closest("[data-hydrate-id]")) continue;
87
+ const id = node.getAttribute("data-hydrate-id");
88
+ const Comp = mods.get(id);
89
+ if (!Comp) {
90
+ log.warn("No module for", id);
91
+ continue;
92
+ }
93
+ let rawProps = {};
94
+ try {
95
+ rawProps = JSON.parse(node.getAttribute("data-hydrate-props") || "{}");
96
+ } catch (e) {
97
+ log.error("Props parse error for", id, e);
98
+ }
99
+ try {
100
+ const element = React.default.createElement(Comp, await reconstructProps(rawProps, mods));
101
+ const root = isNavigation ? createRoot(node) : hydrateRoot(node, element);
102
+ if (isNavigation) root.render(element);
103
+ activeRoots.push(root);
104
+ log.verbose("\u2713 Mounted:", id);
105
+ } catch (err) {
106
+ log.error("\u2717 Mount failed:", id, err);
107
+ }
108
+ }
109
+ }
110
+ function syncAttrs(live, next) {
111
+ for (const { name, value } of Array.from(next.attributes)) {
112
+ live.setAttribute(name, value);
113
+ }
114
+ for (const { name } of Array.from(live.attributes)) {
115
+ if (!next.hasAttribute(name)) live.removeAttribute(name);
116
+ }
117
+ }
118
+ function setupNavigation(log) {
119
+ window.addEventListener("locationchange", async ({ detail: { href, hmr } }) => {
120
+ try {
121
+ const fetchUrl = hmr ? href + (href.includes("?") ? "&" : "?") + "__hmr=1" : href;
122
+ const response = await fetch(fetchUrl, { headers: { Accept: "text/html" } });
123
+ if (!response.ok) {
124
+ log.error("Navigation fetch failed:", response.status);
125
+ return;
126
+ }
127
+ const parser = new DOMParser();
128
+ const doc = parser.parseFromString(await response.text(), "text/html");
129
+ const newApp = doc.getElementById("app");
130
+ const currApp = document.getElementById("app");
131
+ if (!newApp || !currApp) return;
132
+ activeRoots.splice(0).forEach((r) => r.unmount());
133
+ currApp.innerHTML = newApp.innerHTML;
134
+ const newDataEl = doc.getElementById("__n_data");
135
+ const currDataEl = document.getElementById("__n_data");
136
+ if (newDataEl && currDataEl) currDataEl.textContent = newDataEl.textContent;
137
+ const newTitle = doc.querySelector("title");
138
+ if (newTitle) document.title = newTitle.textContent ?? "";
139
+ syncAttrs(document.documentElement, doc.documentElement);
140
+ syncAttrs(document.body, doc.body);
141
+ const navData = JSON.parse(currDataEl?.textContent ?? "{}");
142
+ log.info("\u{1F504} Route \u2192", href, "\u2014 mounting", navData.hydrateIds?.length ?? 0, "component(s)");
143
+ const mods = await loadModules(navData.allIds ?? [], log, String(Date.now()));
144
+ await mountNodes(mods, log, true);
145
+ window.scrollTo(0, 0);
146
+ log.info("\u{1F389} Navigation complete:", href);
147
+ } catch (err) {
148
+ log.error("Navigation error, falling back to full reload:", err);
149
+ window.location.href = href;
150
+ }
151
+ });
152
+ }
153
+ async function initRuntime(data) {
154
+ const log = makeLogger(data.debug ?? "silent");
155
+ log.info("\u{1F680} Partial hydration:", data.hydrateIds.length, "root component(s)");
156
+ setupNavigation(log);
157
+ const mods = await loadModules(data.allIds, log);
158
+ await mountNodes(mods, log, false);
159
+ log.info("\u{1F389} Done!");
160
+ setupLocationChangeMonitor();
161
+ }
162
+ export {
163
+ initRuntime,
164
+ setupLocationChangeMonitor
165
+ };
166
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/bundle.ts"],
4
+ "sourcesContent": ["/**\r\n * bundle.ts \u2014 NukeJS Client Runtime\r\n *\r\n * This file is compiled by esbuild into /__n.js and served to every page.\r\n * It provides:\r\n *\r\n * initRuntime(data) \u2014 called once per page load to hydrate\r\n * \"use client\" components and wire up SPA nav\r\n * setupLocationChangeMonitor() \u2014 patches history.pushState/replaceState so\r\n * SPA navigation fires a 'locationchange' event\r\n *\r\n * Hydration model (partial hydration):\r\n * - The server renders the full page to HTML, wrapping each client component\r\n * in a <span data-hydrate-id=\"cc_\u2026\" data-hydrate-props=\"\u2026\"> marker.\r\n * - initRuntime loads the matching JS bundle for each marker and calls\r\n * hydrateRoot() on it, letting React take over just that subtree.\r\n * - Props serialized by the server may include nested React elements\r\n * (serialized as { __re: 'html'|'client', \u2026 }), which are reconstructed\r\n * back into React.createElement calls before mounting.\r\n *\r\n * SPA navigation:\r\n * - Link clicks / programmatic navigation dispatch a 'locationchange' event.\r\n * - The handler fetches the target URL as HTML, diffs the #app container,\r\n * unmounts the old React roots, and re-hydrates the new ones.\r\n * - HMR navigations add ?__hmr=1 so the server skips client-SSR (faster).\r\n */\r\n\r\n// \u2500\u2500\u2500 History patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Patches history.pushState and history.replaceState to fire a custom\r\n * 'locationchange' event on window. Also listens to 'popstate' for\r\n * back/forward navigation.\r\n *\r\n * This must be called after initRuntime sets up the navigation listener so\r\n * there's no race between the event firing and the listener being registered.\r\n */\r\nexport function setupLocationChangeMonitor(): void {\r\n const originalPushState = window.history.pushState.bind(window.history);\r\n const originalReplaceState = window.history.replaceState.bind(window.history);\r\n\r\n const dispatch = (href?: any) =>\r\n window.dispatchEvent(new CustomEvent('locationchange', { detail: { href } }));\r\n\r\n window.history.pushState = function (...args) {\r\n originalPushState(...args);\r\n dispatch(args[2]); // args[2] is the URL\r\n };\r\n\r\n window.history.replaceState = function (...args) {\r\n originalReplaceState(...args);\r\n dispatch(args[2]);\r\n };\r\n\r\n // Back/forward navigation via the browser's native UI.\r\n window.addEventListener('popstate', () => dispatch(window.location.pathname));\r\n}\r\n\r\n// \u2500\u2500\u2500 Logger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\ntype ClientDebugLevel = 'silent' | 'error' | 'info' | 'verbose';\r\n\r\n/**\r\n * Returns a thin logger whose methods are no-ops unless `level` allows them.\r\n * The server embeds the active debug level in the __n_data JSON blob so the\r\n * client respects the same setting as the server.\r\n */\r\nfunction makeLogger(level: ClientDebugLevel) {\r\n return {\r\n verbose: (...a: any[]) => { if (level === 'verbose') console.log(...a); },\r\n info: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.log(...a); },\r\n warn: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.warn(...a); },\r\n error: (...a: any[]) => { if (level !== 'silent') console.error(...a); },\r\n };\r\n}\r\n\r\n// \u2500\u2500\u2500 Serialized node types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** The wire format for React elements embedded in hydration props. */\r\ntype SerializedNode =\r\n | null\r\n | undefined\r\n | string\r\n | number\r\n | boolean\r\n | SerializedNode[]\r\n | { __re: 'html'; tag: string; props: Record<string, any> } // native DOM element\r\n | { __re: 'client'; componentId: string; props: Record<string, any> } // client component\r\n | Record<string, any>; // plain object\r\n\r\ntype ModuleMap = Map<string, any>; // componentId \u2192 default export\r\n\r\n// \u2500\u2500\u2500 Prop reconstruction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Recursively turns the server's serialized node tree back into real React\r\n * elements so they can be passed as props to hydrated components.\r\n *\r\n * The server serializes JSX passed as props (e.g. `<Button icon={<Icon />}>`)\r\n * into a JSON-safe format. This function reverses that process.\r\n */\r\nasync function reconstructElement(node: SerializedNode, mods: ModuleMap): Promise<any> {\r\n if (node === null || node === undefined) return node;\r\n if (typeof node !== 'object') return node; // primitive \u2014 pass through\r\n\r\n if (Array.isArray(node)) {\r\n const items = await Promise.all(node.map(n => reconstructElement(n, mods)));\r\n // Add index-based keys to any React elements in the array so React doesn't\r\n // warn about \"Each child in a list should have a unique key prop\".\r\n const React = await import('react');\r\n return items.map((el, i) =>\r\n el && typeof el === 'object' && el.$$typeof\r\n ? React.default.cloneElement(el, { key: el.key ?? i })\r\n : el,\r\n );\r\n }\r\n\r\n // Client component \u2014 look up the loaded module by ID.\r\n if ((node as any).__re === 'client') {\r\n const n = node as { __re: 'client'; componentId: string; props: Record<string, any> };\r\n const Comp = mods.get(n.componentId);\r\n if (!Comp) return null;\r\n const React = await import('react');\r\n return React.default.createElement(Comp, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Native HTML element (e.g. <div>, <span>).\r\n if ((node as any).__re === 'html') {\r\n const n = node as { __re: 'html'; tag: string; props: Record<string, any> };\r\n const React = await import('react');\r\n return React.default.createElement(n.tag, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Plain object \u2014 reconstruct each value.\r\n return node;\r\n}\r\n\r\n/** Reconstructs every value in a props object, handling nested serialized nodes. */\r\nasync function reconstructProps(\r\n props: Record<string, any> | null | undefined,\r\n mods: ModuleMap,\r\n): Promise<Record<string, any>> {\r\n if (!props || typeof props !== 'object' || Array.isArray(props))\r\n return reconstructElement(props as any, mods);\r\n\r\n const out: Record<string, any> = {};\r\n for (const [k, v] of Object.entries(props))\r\n out[k] = await reconstructElement(v, mods);\r\n return out;\r\n}\r\n\r\n// \u2500\u2500\u2500 Module loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Dynamically imports each client component bundle from /__client-component/.\r\n * All fetches are issued in parallel; failures are logged but do not abort\r\n * the rest of the hydration pass.\r\n *\r\n * @param bust Optional cache-busting suffix appended as `?t=<bust>`.\r\n * Used during HMR navigation to bypass the module cache.\r\n */\r\nasync function loadModules(\r\n ids: string[],\r\n log: ReturnType<typeof makeLogger>,\r\n bust = '',\r\n): Promise<ModuleMap> {\r\n const mods: ModuleMap = new Map();\r\n await Promise.all(\r\n ids.map(async (id) => {\r\n try {\r\n const url = `/__client-component/${id}.js` + (bust ? `?t=${bust}` : '');\r\n const m = await import(url);\r\n mods.set(id, m.default);\r\n log.verbose('\u2713 Loaded:', id);\r\n } catch (err) {\r\n log.error('\u2717 Load failed:', id, err);\r\n }\r\n }),\r\n );\r\n return mods;\r\n}\r\n\r\n// \u2500\u2500\u2500 Root mounting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** All active React roots \u2014 tracked so they can be unmounted before navigation. */\r\ntype ReactRoot = { unmount(): void };\r\nconst activeRoots: ReactRoot[] = [];\r\n\r\n/**\r\n * Finds every `[data-hydrate-id]` span in the document and either:\r\n * - hydrateRoot() \u2014 on initial page load (server HTML already present)\r\n * - createRoot() \u2014 after SPA navigation (innerHTML was set by us)\r\n *\r\n * Nested markers (a client component inside another client component) are\r\n * skipped here because the parent's React tree will handle its children.\r\n */\r\nasync function mountNodes(\r\n mods: ModuleMap,\r\n log: ReturnType<typeof makeLogger>,\r\n isNavigation: boolean,\r\n): Promise<void> {\r\n const { hydrateRoot, createRoot } = await import('react-dom/client');\r\n const React = await import('react');\r\n\r\n const nodes = document.querySelectorAll<HTMLElement>('[data-hydrate-id]');\r\n log.verbose('Found', nodes.length, 'hydration point(s)');\r\n\r\n for (const node of nodes) {\r\n // Skip nested markers \u2014 the outer component owns its children.\r\n if (node.parentElement?.closest('[data-hydrate-id]')) continue;\r\n\r\n const id = node.getAttribute('data-hydrate-id')!;\r\n const Comp = mods.get(id);\r\n if (!Comp) { log.warn('No module for', id); continue; }\r\n\r\n // Deserialize props from the data attribute (JSON set by the server).\r\n let rawProps: Record<string, any> = {};\r\n try {\r\n rawProps = JSON.parse(node.getAttribute('data-hydrate-props') || '{}');\r\n } catch (e) {\r\n log.error('Props parse error for', id, e);\r\n }\r\n\r\n try {\r\n const element = React.default.createElement(Comp, await reconstructProps(rawProps, mods));\r\n\r\n // hydrateRoot reconciles React's virtual DOM against the server-rendered\r\n // HTML without fully re-rendering. createRoot replaces the content\r\n // entirely \u2014 safe after navigation because we set innerHTML ourselves.\r\n const root = isNavigation ? createRoot(node) : hydrateRoot(node, element);\r\n if (isNavigation) (root as any).render(element);\r\n activeRoots.push(root);\r\n log.verbose('\u2713 Mounted:', id);\r\n } catch (err) {\r\n log.error('\u2717 Mount failed:', id, err);\r\n }\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 SPA navigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Listens for 'locationchange' events (fired by setupLocationChangeMonitor\r\n * or by Link clicks) and performs a soft navigation:\r\n *\r\n * 1. Fetch the target URL as HTML (adds ?__hmr=1 during HMR updates so the\r\n * server skips client-side SSR for a faster response).\r\n * 2. Parse the response with DOMParser.\r\n * 3. Replace #app innerHTML and __n_data.\r\n * 4. Unmount old React roots, then re-hydrate new ones.\r\n * 5. Scroll to top.\r\n *\r\n * Falls back to a full page reload if anything goes wrong.\r\n */\r\n/**\r\n * Syncs attributes from a parsed element onto the live document element.\r\n * Adds/updates attributes present in `next`, and removes any that were set\r\n * on `live` but are absent in `next` (so stale bodyAttrs/htmlAttrs are cleared).\r\n */\r\nfunction syncAttrs(live: Element, next: Element): void {\r\n // Apply / update attributes from the new document.\r\n for (const { name, value } of Array.from(next.attributes)) {\r\n live.setAttribute(name, value);\r\n }\r\n // Remove attributes that no longer exist in the new document.\r\n for (const { name } of Array.from(live.attributes)) {\r\n if (!next.hasAttribute(name)) live.removeAttribute(name);\r\n }\r\n}\r\n\r\nfunction setupNavigation(log: ReturnType<typeof makeLogger>): void {\r\n window.addEventListener('locationchange', async ({ detail: { href, hmr } }: any) => {\r\n try {\r\n // Append ?__hmr=1 for HMR-triggered reloads so SSR skips the slower\r\n // client-component renderToString path.\r\n const fetchUrl = hmr\r\n ? href + (href.includes('?') ? '&' : '?') + '__hmr=1'\r\n : href;\r\n\r\n const response = await fetch(fetchUrl, { headers: { Accept: 'text/html' } });\r\n if (!response.ok) {\r\n log.error('Navigation fetch failed:', response.status);\r\n return;\r\n }\r\n\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(await response.text(), 'text/html');\r\n const newApp = doc.getElementById('app');\r\n const currApp = document.getElementById('app');\r\n if (!newApp || !currApp) return;\r\n\r\n // Tear down existing React trees before mutating the DOM \u2014 avoids React\r\n // warnings about unmounting from a detached node.\r\n activeRoots.splice(0).forEach(r => r.unmount());\r\n\r\n // Swap content in-place (avoids a full document.write / navigation).\r\n currApp.innerHTML = newApp.innerHTML;\r\n\r\n // Update the runtime data blob so subsequent navigations use the new page's\r\n // client component IDs.\r\n const newDataEl = doc.getElementById('__n_data');\r\n const currDataEl = document.getElementById('__n_data');\r\n if (newDataEl && currDataEl) currDataEl.textContent = newDataEl.textContent;\r\n\r\n // Update <title>.\r\n const newTitle = doc.querySelector('title');\r\n if (newTitle) document.title = newTitle.textContent ?? '';\r\n\r\n // Sync <html> attributes (e.g. lang, class, style from useHtml({ htmlAttrs })).\r\n syncAttrs(document.documentElement, doc.documentElement);\r\n\r\n // Sync <body> attributes (e.g. style, class from useHtml({ bodyAttrs })).\r\n syncAttrs(document.body, doc.body);\r\n\r\n const navData = JSON.parse(currDataEl?.textContent ?? '{}') as RuntimeData;\r\n log.info('\uD83D\uDD04 Route \u2192', href, '\u2014 mounting', navData.hydrateIds?.length ?? 0, 'component(s)');\r\n\r\n // Load bundles with a cache-buster timestamp so stale modules are evicted.\r\n const mods = await loadModules(navData.allIds ?? [], log, String(Date.now()));\r\n await mountNodes(mods, log, true);\r\n\r\n window.scrollTo(0, 0);\r\n log.info('\uD83C\uDF89 Navigation complete:', href);\r\n } catch (err) {\r\n log.error('Navigation error, falling back to full reload:', err);\r\n window.location.href = href;\r\n }\r\n });\r\n}\r\n\r\n// \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** Shape of the JSON blob embedded as #__n_data in every SSR page. */\r\nexport interface RuntimeData {\r\n /** IDs of client components actually rendered on this page (subset of allIds). */\r\n hydrateIds: string[];\r\n /** All client component IDs reachable from this page, including layouts.\r\n * Pre-loaded so SPA navigations to related pages feel instant. */\r\n allIds: string[];\r\n url: string;\r\n params: Record<string, any>;\r\n debug: ClientDebugLevel;\r\n}\r\n\r\n/**\r\n * Bootstraps the NukeJS client runtime.\r\n *\r\n * Called once per page load from the inline <script type=\"module\"> injected\r\n * by the SSR renderer:\r\n *\r\n * ```js\r\n * const { initRuntime } = await import('nukejs');\r\n * const data = JSON.parse(document.getElementById('__n_data').textContent);\r\n * await initRuntime(data);\r\n * ```\r\n *\r\n * Order of operations:\r\n * 1. Create the logger at the configured debug level.\r\n * 2. Wire up SPA navigation listener.\r\n * 3. Load all client component bundles in parallel.\r\n * 4. Hydrate every [data-hydrate-id] node.\r\n * 5. Patch history.pushState/replaceState so Link clicks trigger navigation.\r\n */\r\nexport async function initRuntime(data: RuntimeData): Promise<void> {\r\n const log = makeLogger(data.debug ?? 'silent');\r\n\r\n log.info('\uD83D\uDE80 Partial hydration:', data.hydrateIds.length, 'root component(s)');\r\n\r\n // Set up navigation first so any 'locationchange' fired during hydration\r\n // is captured.\r\n setupNavigation(log);\r\n\r\n // Load all component bundles (not just hydrateIds) so SPA navigations can\r\n // mount components for other pages without an extra network round-trip.\r\n const mods = await loadModules(data.allIds, log);\r\n await mountNodes(mods, log, false);\r\n\r\n log.info('\uD83C\uDF89 Done!');\r\n\r\n // Patch history last so pushState calls during hydration (e.g. redirect\r\n // side-effects) don't trigger a navigation before roots are ready.\r\n setupLocationChangeMonitor();\r\n}"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * bundler.ts — Dev-Mode On-Demand Bundler
3
+ *
4
+ * Handles the three internal JS routes served only in `nuke dev`:
5
+ *
6
+ * /__react.js — Full React + ReactDOM browser bundle.
7
+ * All hooks, jsx-runtime, hydrateRoot, createRoot.
8
+ * Served once and cached by the browser.
9
+ *
10
+ * /__n.js — NukeJS client runtime (bundle.ts compiled to ESM).
11
+ * Provides initRuntime and SPA navigation.
12
+ *
13
+ * /__client-component/<id> — Individual "use client" component bundles.
14
+ * Built on-demand the first time they're requested.
15
+ * Re-built on every request in dev (no disk cache).
16
+ *
17
+ * In production (`nuke build`), equivalent bundles are written to dist/static/
18
+ * by build-common.ts instead of being served dynamically.
19
+ */
20
+ import type { ServerResponse } from 'http';
21
+ /**
22
+ * Bundles a single "use client" file for the browser.
23
+ *
24
+ * React and react-dom/client are kept external so the importmap can resolve
25
+ * them to the already-loaded /__react.js bundle (avoids shipping React twice).
26
+ *
27
+ * @param filePath Absolute path to the source file.
28
+ * @returns ESM string ready to serve as application/javascript.
29
+ */
30
+ export declare function bundleClientComponent(filePath: string): Promise<string>;
31
+ /**
32
+ * Looks up a client component by its content-hash ID (e.g. `cc_a1b2c3d4`),
33
+ * bundles it on-demand, and writes the result to the HTTP response.
34
+ *
35
+ * The ID→path mapping comes from the component analyzer cache, which is
36
+ * populated during SSR as pages and their layouts are rendered.
37
+ */
38
+ export declare function serveClientComponentBundle(componentId: string, res: ServerResponse): Promise<void>;
39
+ /**
40
+ * Builds and serves the unified React browser bundle to /__react.js.
41
+ *
42
+ * Exports every public React API so client components can import from 'react'
43
+ * or 'react-dom/client' and have them resolve via the importmap to this single
44
+ * pre-loaded bundle — no duplicate copies of React in the browser.
45
+ *
46
+ * esbuild aliases point at the project's installed React version so the bundle
47
+ * always matches what the server is actually running.
48
+ */
49
+ export declare function serveReactBundle(res: ServerResponse): Promise<void>;
50
+ /**
51
+ * Bundles and serves the NukeJS client runtime to /__n.js.
52
+ *
53
+ * The entry point is bundle.ts (or bundle.js in production dist/).
54
+ * React is kept external so it resolves via the importmap to /__react.js.
55
+ *
56
+ * Minified because this script is loaded on every page request.
57
+ */
58
+ export declare function serveNukeBundle(res: ServerResponse): Promise<void>;
@@ -0,0 +1,100 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { build } from "esbuild";
4
+ import { log } from "./logger.js";
5
+ import { getComponentById } from "./component-analyzer.js";
6
+ let reactBundlePromise = null;
7
+ let nukeBundlePromise = null;
8
+ async function bundleClientComponent(filePath) {
9
+ const result = await build({
10
+ entryPoints: [filePath],
11
+ bundle: true,
12
+ format: "esm",
13
+ platform: "browser",
14
+ write: false,
15
+ jsx: "automatic",
16
+ // Keep React external — resolved by the importmap to /__react.js
17
+ external: ["react", "react-dom/client", "react/jsx-runtime"]
18
+ });
19
+ return result.outputFiles[0].text;
20
+ }
21
+ async function serveClientComponentBundle(componentId, res) {
22
+ const filePath = getComponentById(componentId);
23
+ if (filePath) {
24
+ log.verbose(`Bundling client component: ${componentId} (${path.basename(filePath)})`);
25
+ res.setHeader("Content-Type", "application/javascript");
26
+ res.end(await bundleClientComponent(filePath));
27
+ return;
28
+ }
29
+ log.error(`Client component not found: ${componentId}`);
30
+ res.statusCode = 404;
31
+ res.end("Client component not found");
32
+ }
33
+ async function serveReactBundle(res) {
34
+ log.verbose("Bundling React runtime");
35
+ if (!reactBundlePromise) {
36
+ reactBundlePromise = build({
37
+ stdin: {
38
+ contents: `
39
+ import React, {
40
+ useState, useEffect, useContext, useReducer, useCallback, useMemo,
41
+ useRef, useImperativeHandle, useLayoutEffect, useDebugValue,
42
+ useDeferredValue, useTransition, useId, useSyncExternalStore,
43
+ useInsertionEffect, createContext, forwardRef, memo, lazy,
44
+ Suspense, Fragment, StrictMode, Component, PureComponent
45
+ } from 'react';
46
+ import { jsx, jsxs } from 'react/jsx-runtime';
47
+ import { hydrateRoot, createRoot } from 'react-dom/client';
48
+
49
+ export {
50
+ useState, useEffect, useContext, useReducer, useCallback, useMemo,
51
+ useRef, useImperativeHandle, useLayoutEffect, useDebugValue,
52
+ useDeferredValue, useTransition, useId, useSyncExternalStore,
53
+ useInsertionEffect, createContext, forwardRef, memo, lazy,
54
+ Suspense, Fragment, StrictMode, Component, PureComponent,
55
+ hydrateRoot, createRoot, jsx, jsxs
56
+ };
57
+ export default React;
58
+ `,
59
+ loader: "ts"
60
+ },
61
+ bundle: true,
62
+ write: false,
63
+ treeShaking: true,
64
+ minify: false,
65
+ format: "esm",
66
+ jsx: "automatic",
67
+ alias: {
68
+ react: path.dirname(fileURLToPath(import.meta.resolve("react/package.json"))),
69
+ "react-dom": path.dirname(fileURLToPath(import.meta.resolve("react-dom/package.json")))
70
+ },
71
+ define: { "process.env.NODE_ENV": '"development"' }
72
+ }).then((r) => r.outputFiles[0].text);
73
+ }
74
+ res.setHeader("Content-Type", "application/javascript");
75
+ res.end(await reactBundlePromise);
76
+ }
77
+ async function serveNukeBundle(res) {
78
+ log.verbose("Bundling nuke runtime");
79
+ if (!nukeBundlePromise) {
80
+ const dir = path.dirname(fileURLToPath(import.meta.url));
81
+ const entry = path.join(dir, `bundle.${dir.endsWith("dist") ? "js" : "ts"}`);
82
+ nukeBundlePromise = build({
83
+ entryPoints: [entry],
84
+ write: false,
85
+ format: "esm",
86
+ minify: true,
87
+ bundle: true,
88
+ external: ["react", "react-dom/client"]
89
+ }).then((r) => r.outputFiles[0].text);
90
+ }
91
+ res.setHeader("Content-Type", "application/javascript");
92
+ res.end(await nukeBundlePromise);
93
+ }
94
+ export {
95
+ bundleClientComponent,
96
+ serveClientComponentBundle,
97
+ serveNukeBundle,
98
+ serveReactBundle
99
+ };
100
+ //# sourceMappingURL=bundler.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/bundler.ts"],
4
+ "sourcesContent": ["/**\n * bundler.ts \u2014 Dev-Mode On-Demand Bundler\n *\n * Handles the three internal JS routes served only in `nuke dev`:\n *\n * /__react.js \u2014 Full React + ReactDOM browser bundle.\n * All hooks, jsx-runtime, hydrateRoot, createRoot.\n * Served once and cached by the browser.\n *\n * /__n.js \u2014 NukeJS client runtime (bundle.ts compiled to ESM).\n * Provides initRuntime and SPA navigation.\n *\n * /__client-component/<id> \u2014 Individual \"use client\" component bundles.\n * Built on-demand the first time they're requested.\n * Re-built on every request in dev (no disk cache).\n *\n * In production (`nuke build`), equivalent bundles are written to dist/static/\n * by build-common.ts instead of being served dynamically.\n */\n\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { build } from 'esbuild';\nimport type { ServerResponse } from 'http';\nimport { log } from './logger';\nimport { getComponentById } from './component-analyzer';\n\n// \u2500\u2500\u2500 Bundle caches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Cache Promises (not just results) so concurrent requests share one build\n// instead of each spawning their own esbuild process.\nlet reactBundlePromise: Promise<string> | null = null;\nlet nukeBundlePromise: Promise<string> | null = null;\n\n// \u2500\u2500\u2500 Client component bundle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bundles a single \"use client\" file for the browser.\n *\n * React and react-dom/client are kept external so the importmap can resolve\n * them to the already-loaded /__react.js bundle (avoids shipping React twice).\n *\n * @param filePath Absolute path to the source file.\n * @returns ESM string ready to serve as application/javascript.\n */\nexport async function bundleClientComponent(filePath: string): Promise<string> {\n const result = await build({\n entryPoints: [filePath],\n bundle: true,\n format: 'esm',\n platform: 'browser',\n write: false,\n jsx: 'automatic',\n // Keep React external \u2014 resolved by the importmap to /__react.js\n external: ['react', 'react-dom/client', 'react/jsx-runtime'],\n });\n return result.outputFiles[0].text;\n}\n\n/**\n * Looks up a client component by its content-hash ID (e.g. `cc_a1b2c3d4`),\n * bundles it on-demand, and writes the result to the HTTP response.\n *\n * The ID\u2192path mapping comes from the component analyzer cache, which is\n * populated during SSR as pages and their layouts are rendered.\n */\nexport async function serveClientComponentBundle(\n componentId: string,\n res: ServerResponse,\n): Promise<void> {\n const filePath = getComponentById(componentId);\n if (filePath) {\n log.verbose(`Bundling client component: ${componentId} (${path.basename(filePath)})`);\n res.setHeader('Content-Type', 'application/javascript');\n res.end(await bundleClientComponent(filePath));\n return;\n }\n\n // ID not found \u2014 either the page hasn't been visited yet (cache is empty)\n // or the ID is stale.\n log.error(`Client component not found: ${componentId}`);\n res.statusCode = 404;\n res.end('Client component not found');\n}\n\n// \u2500\u2500\u2500 React bundle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Builds and serves the unified React browser bundle to /__react.js.\n *\n * Exports every public React API so client components can import from 'react'\n * or 'react-dom/client' and have them resolve via the importmap to this single\n * pre-loaded bundle \u2014 no duplicate copies of React in the browser.\n *\n * esbuild aliases point at the project's installed React version so the bundle\n * always matches what the server is actually running.\n */\nexport async function serveReactBundle(res: ServerResponse): Promise<void> {\n log.verbose('Bundling React runtime');\n\n if (!reactBundlePromise) {\n reactBundlePromise = build({\n stdin: {\n contents: `\n import React, {\n useState, useEffect, useContext, useReducer, useCallback, useMemo,\n useRef, useImperativeHandle, useLayoutEffect, useDebugValue,\n useDeferredValue, useTransition, useId, useSyncExternalStore,\n useInsertionEffect, createContext, forwardRef, memo, lazy,\n Suspense, Fragment, StrictMode, Component, PureComponent\n } from 'react';\n import { jsx, jsxs } from 'react/jsx-runtime';\n import { hydrateRoot, createRoot } from 'react-dom/client';\n\n export {\n useState, useEffect, useContext, useReducer, useCallback, useMemo,\n useRef, useImperativeHandle, useLayoutEffect, useDebugValue,\n useDeferredValue, useTransition, useId, useSyncExternalStore,\n useInsertionEffect, createContext, forwardRef, memo, lazy,\n Suspense, Fragment, StrictMode, Component, PureComponent,\n hydrateRoot, createRoot, jsx, jsxs\n };\n export default React;\n `,\n loader: 'ts',\n },\n bundle: true,\n write: false,\n treeShaking: true,\n minify: false,\n format: 'esm',\n jsx: 'automatic',\n alias: {\n react: path.dirname(fileURLToPath(import.meta.resolve('react/package.json'))),\n 'react-dom': path.dirname(fileURLToPath(import.meta.resolve('react-dom/package.json'))),\n },\n define: { 'process.env.NODE_ENV': '\"development\"' },\n }).then(r => r.outputFiles[0].text);\n }\n\n res.setHeader('Content-Type', 'application/javascript');\n res.end(await reactBundlePromise);\n}\n\n// \u2500\u2500\u2500 NukeJS runtime bundle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bundles and serves the NukeJS client runtime to /__n.js.\n *\n * The entry point is bundle.ts (or bundle.js in production dist/).\n * React is kept external so it resolves via the importmap to /__react.js.\n *\n * Minified because this script is loaded on every page request.\n */\nexport async function serveNukeBundle(res: ServerResponse): Promise<void> {\n log.verbose('Bundling nuke runtime');\n\n if (!nukeBundlePromise) {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n const entry = path.join(dir, `bundle.${dir.endsWith('dist') ? 'js' : 'ts'}`);\n nukeBundlePromise = build({\n entryPoints: [entry],\n write: false,\n format: 'esm',\n minify: true,\n bundle: true,\n external: ['react', 'react-dom/client'],\n }).then(r => r.outputFiles[0].text);\n }\n\n res.setHeader('Content-Type', 'application/javascript');\n res.end(await nukeBundlePromise);\n}"],
5
+ "mappings": "AAoBA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,aAAa;AAEtB,SAAS,WAAW;AACpB,SAAS,wBAAwB;AAMjC,IAAI,qBAA6C;AACjD,IAAI,oBAA4C;AAahD,eAAsB,sBAAsB,UAAmC;AAC7E,QAAM,SAAS,MAAM,MAAM;AAAA,IACzB,aAAa,CAAC,QAAQ;AAAA,IACtB,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA;AAAA,IAEL,UAAU,CAAC,SAAS,oBAAoB,mBAAmB;AAAA,EAC7D,CAAC;AACD,SAAO,OAAO,YAAY,CAAC,EAAE;AAC/B;AASA,eAAsB,2BACpB,aACA,KACe;AACf,QAAM,WAAW,iBAAiB,WAAW;AAC7C,MAAI,UAAU;AACZ,QAAI,QAAQ,8BAA8B,WAAW,KAAK,KAAK,SAAS,QAAQ,CAAC,GAAG;AACpF,QAAI,UAAU,gBAAgB,wBAAwB;AACtD,QAAI,IAAI,MAAM,sBAAsB,QAAQ,CAAC;AAC7C;AAAA,EACF;AAIA,MAAI,MAAM,+BAA+B,WAAW,EAAE;AACtD,MAAI,aAAa;AACjB,MAAI,IAAI,4BAA4B;AACtC;AAcA,eAAsB,iBAAiB,KAAoC;AACzE,MAAI,QAAQ,wBAAwB;AAEpC,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,MAAM;AAAA,MACzB,OAAO;AAAA,QACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAqBV,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,OAAO;AAAA,QACL,OAAO,KAAK,QAAQ,cAAc,YAAY,QAAQ,oBAAoB,CAAC,CAAC;AAAA,QAC5E,aAAa,KAAK,QAAQ,cAAc,YAAY,QAAQ,wBAAwB,CAAC,CAAC;AAAA,MACxF;AAAA,MACA,QAAQ,EAAE,wBAAwB,gBAAgB;AAAA,IACpD,CAAC,EAAE,KAAK,OAAK,EAAE,YAAY,CAAC,EAAE,IAAI;AAAA,EACpC;AAEA,MAAI,UAAU,gBAAgB,wBAAwB;AACtD,MAAI,IAAI,MAAM,kBAAkB;AAClC;AAYA,eAAsB,gBAAgB,KAAoC;AACxE,MAAI,QAAQ,uBAAuB;AAEnC,MAAI,CAAC,mBAAmB;AACtB,UAAM,MAAM,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACvD,UAAM,QAAQ,KAAK,KAAK,KAAK,UAAU,IAAI,SAAS,MAAM,IAAI,OAAO,IAAI,EAAE;AAC3E,wBAAoB,MAAM;AAAA,MACxB,aAAa,CAAC,KAAK;AAAA,MACnB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU,CAAC,SAAS,kBAAkB;AAAA,IACxC,CAAC,EAAE,KAAK,OAAK,EAAE,YAAY,CAAC,EAAE,IAAI;AAAA,EACpC;AAEA,MAAI,UAAU,gBAAgB,wBAAwB;AACtD,MAAI,IAAI,MAAM,iBAAiB;AACjC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * component-analyzer.ts — Static Import Analyzer & Client Component Registry
3
+ *
4
+ * This module solves a core problem in NukeJS's partial hydration model:
5
+ * the server needs to know *at render time* which components in a page's
6
+ * import tree are "use client" boundaries so it can:
7
+ *
8
+ * 1. Emit <span data-hydrate-id="…"> markers instead of rendering them.
9
+ * 2. Inject the matching bundle URLs into the page's runtime data blob.
10
+ * 3. Serialize the props passed to those components so the browser can
11
+ * reconstruct them after loading the bundle.
12
+ *
13
+ * How it works:
14
+ * - analyzeComponent() checks whether a file starts with "use client"
15
+ * and assigns a stable content-hash ID if it does.
16
+ * - extractImports() parses `import … from '…'` statements with a
17
+ * regex and resolves relative/absolute paths.
18
+ * - findClientComponentsInTree() recursively walks the import graph, stopping
19
+ * at client boundaries (they own their subtree).
20
+ *
21
+ * Results are memoised in `componentCache` (process-lifetime) so repeated SSR
22
+ * renders don't re-read and re-hash files they've already seen.
23
+ *
24
+ * ID scheme:
25
+ * The ID for a client component is `cc_` + the first 8 hex chars of the MD5
26
+ * hash of its path relative to pagesDir. This is stable across restarts and
27
+ * matches what the browser will request from /__client-component/<id>.js.
28
+ */
29
+ export interface ComponentInfo {
30
+ filePath: string;
31
+ /** True when the file's first non-comment line is "use client". */
32
+ isClientComponent: boolean;
33
+ /** Stable hash-based ID, present only for client components. */
34
+ clientComponentId?: string;
35
+ }
36
+ /**
37
+ * Analyses a component file and returns cached results on subsequent calls.
38
+ *
39
+ * @param filePath Absolute path to the source file.
40
+ * @param pagesDir Absolute path to the pages root (used for ID generation).
41
+ */
42
+ export declare function analyzeComponent(filePath: string, pagesDir: string): ComponentInfo;
43
+ /**
44
+ * Recursively walks the import graph from `filePath`, collecting every
45
+ * "use client" file encountered.
46
+ *
47
+ * The walk stops at client boundaries: a "use client" file is collected and
48
+ * its own imports are NOT walked (the client runtime handles their subtree).
49
+ *
50
+ * The `visited` set prevents infinite loops from circular imports.
51
+ *
52
+ * @returns Map<id, absoluteFilePath> for every client component reachable
53
+ * from `filePath` (including `filePath` itself if it's a client).
54
+ */
55
+ export declare function findClientComponentsInTree(filePath: string, pagesDir: string, visited?: Set<string>): Map<string, string>;
56
+ /**
57
+ * Looks up the absolute file path for a client component by its ID.
58
+ * O(1) reverse lookup — avoids the O(n) linear scan in bundler.ts.
59
+ *
60
+ * Returns undefined when the ID is not in the cache (page not yet visited
61
+ * in dev, or stale ID after a file change).
62
+ */
63
+ export declare function getComponentById(id: string): string | undefined;
64
+ /** Returns the live component cache (used by bundler.ts for ID→path lookup). */
65
+ export declare function getComponentCache(): Map<string, ComponentInfo>;
66
+ /**
67
+ * Removes a single file's analysis entry from the cache.
68
+ * Call this whenever a source file changes in dev mode so the next render
69
+ * re-analyses the file (picks up added/removed "use client" directives and
70
+ * changed import graphs).
71
+ */
72
+ export declare function invalidateComponentCache(filePath: string): void;
@@ -0,0 +1,102 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import crypto from "crypto";
4
+ import { fileURLToPath } from "url";
5
+ const componentCache = /* @__PURE__ */ new Map();
6
+ function isClientComponent(filePath) {
7
+ const content = fs.readFileSync(filePath, "utf-8");
8
+ for (const line of content.split("\n").slice(0, 5)) {
9
+ const trimmed = line.trim();
10
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*")) continue;
11
+ if (/^["']use client["'];?$/.test(trimmed)) return true;
12
+ break;
13
+ }
14
+ return false;
15
+ }
16
+ function getClientComponentId(filePath, pagesDir) {
17
+ const hash = crypto.createHash("md5").update(path.relative(pagesDir, filePath)).digest("hex").substring(0, 8);
18
+ return `cc_${hash}`;
19
+ }
20
+ function analyzeComponent(filePath, pagesDir) {
21
+ if (componentCache.has(filePath)) return componentCache.get(filePath);
22
+ const isClient = isClientComponent(filePath);
23
+ const info = {
24
+ filePath,
25
+ isClientComponent: isClient,
26
+ clientComponentId: isClient ? getClientComponentId(filePath, pagesDir) : void 0
27
+ };
28
+ componentCache.set(filePath, info);
29
+ return info;
30
+ }
31
+ function extractImports(filePath) {
32
+ const content = fs.readFileSync(filePath, "utf-8");
33
+ const dir = path.dirname(filePath);
34
+ const imports = [];
35
+ const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g;
36
+ let match;
37
+ while ((match = importRegex.exec(content)) !== null) {
38
+ const importPath = match[1];
39
+ if (importPath === "nukejs") {
40
+ const selfDir = path.dirname(fileURLToPath(import.meta.url));
41
+ for (const candidate of [
42
+ path.join(selfDir, "index.ts"),
43
+ path.join(selfDir, "index.js")
44
+ ]) {
45
+ if (fs.existsSync(candidate)) {
46
+ imports.push(candidate);
47
+ break;
48
+ }
49
+ }
50
+ continue;
51
+ }
52
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) continue;
53
+ let resolved = path.resolve(dir, importPath);
54
+ if (!fs.existsSync(resolved)) {
55
+ for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
56
+ const candidate = resolved + ext;
57
+ if (fs.existsSync(candidate)) {
58
+ resolved = candidate;
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ if (fs.existsSync(resolved)) imports.push(resolved);
64
+ }
65
+ return imports;
66
+ }
67
+ function findClientComponentsInTree(filePath, pagesDir, visited = /* @__PURE__ */ new Set()) {
68
+ const clientComponents = /* @__PURE__ */ new Map();
69
+ if (visited.has(filePath)) return clientComponents;
70
+ visited.add(filePath);
71
+ const info = analyzeComponent(filePath, pagesDir);
72
+ if (info.isClientComponent && info.clientComponentId) {
73
+ clientComponents.set(info.clientComponentId, filePath);
74
+ return clientComponents;
75
+ }
76
+ for (const importPath of extractImports(filePath)) {
77
+ for (const [id, p] of findClientComponentsInTree(importPath, pagesDir, visited)) {
78
+ clientComponents.set(id, p);
79
+ }
80
+ }
81
+ return clientComponents;
82
+ }
83
+ function getComponentById(id) {
84
+ for (const [filePath, info] of componentCache) {
85
+ if (info.clientComponentId === id) return filePath;
86
+ }
87
+ return void 0;
88
+ }
89
+ function getComponentCache() {
90
+ return componentCache;
91
+ }
92
+ function invalidateComponentCache(filePath) {
93
+ componentCache.delete(filePath);
94
+ }
95
+ export {
96
+ analyzeComponent,
97
+ findClientComponentsInTree,
98
+ getComponentById,
99
+ getComponentCache,
100
+ invalidateComponentCache
101
+ };
102
+ //# sourceMappingURL=component-analyzer.js.map