nukejs 0.0.13 → 0.0.14

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.
@@ -149,7 +149,7 @@ function collectServerPages(pagesDir) {
149
149
  return walkFiles(pagesDir).filter((relPath) => {
150
150
  const stem = path.basename(relPath, path.extname(relPath));
151
151
  if (stem === "layout" || stem === "_404" || stem === "_500") return false;
152
- return isServerComponent(path.join(pagesDir, relPath));
152
+ return true;
153
153
  }).map((relPath) => ({
154
154
  ...analyzeFile(relPath, "page"),
155
155
  absPath: path.join(pagesDir, relPath)
@@ -426,7 +426,8 @@ function buildWrapperAttrString(attrs: Record<string, any>): string {
426
426
  .map(([key, value]) => {
427
427
  if (key === 'className') key = 'class';
428
428
  if (key === 'style' && typeof value === 'object') {
429
- const css = Object.entries(value as Record<string, any>)
429
+ // Always prepend display:contents so the wrapper span is invisible to layout.
430
+ const css = 'display:contents;' + Object.entries(value as Record<string, any>)
430
431
  .map(([p, val]) => \`\${p.replace(/[A-Z]/g, m => \`-\${m.toLowerCase()}\`)}:\${escapeHtml(String(val))}\`)
431
432
  .join(';');
432
433
  return \`style="\${css}"\`;
@@ -436,6 +437,8 @@ function buildWrapperAttrString(attrs: Record<string, any>): string {
436
437
  return \`\${key}="\${escapeHtml(String(value))}"\`;
437
438
  })
438
439
  .filter(Boolean);
440
+ // When no style prop was passed, still emit display:contents.
441
+ if (!('style' in attrs)) parts.push('style="display:contents"');
439
442
  return parts.length ? ' ' + parts.join(' ') : '';
440
443
  }
441
444
 
package/dist/bundle.js CHANGED
@@ -10,7 +10,7 @@ function setupLocationChangeMonitor() {
10
10
  originalReplaceState(...args);
11
11
  dispatch(args[2]);
12
12
  };
13
- window.addEventListener("popstate", () => dispatch(window.location.pathname));
13
+ window.addEventListener("popstate", () => dispatch(window.location.pathname + window.location.search));
14
14
  }
15
15
  function makeLogger(level) {
16
16
  return {
@@ -248,7 +248,15 @@ function syncAttrs(live, next) {
248
248
  if (!next.hasAttribute(name)) live.removeAttribute(name);
249
249
  }
250
250
  function setupNavigation(log) {
251
+ let hmrNavPending = false;
251
252
  window.addEventListener("locationchange", async ({ detail: { href, hmr } }) => {
253
+ if (hmr) {
254
+ if (hmrNavPending) {
255
+ log.info("[HMR] Navigation already in flight \u2014 skipping duplicate for", href);
256
+ return;
257
+ }
258
+ hmrNavPending = true;
259
+ }
252
260
  try {
253
261
  const fetchUrl = hmr ? href + (href.includes("?") ? "&" : "?") + "__hmr=1" : href;
254
262
  const response = await fetch(fetchUrl, { headers: { Accept: "text/html" } });
@@ -279,7 +287,7 @@ function setupNavigation(log) {
279
287
  activeRoots.splice(0).forEach((r) => r.unmount());
280
288
  const navData = JSON.parse(currDataEl?.textContent ?? "{}");
281
289
  log.info("\u{1F504} Route \u2192", href, "\u2014 mounting", navData.hydrateIds?.length ?? 0, "component(s)");
282
- const mods = await loadModules(navData.allIds ?? [], log, String(Date.now()));
290
+ const mods = await loadModules(navData.allIds ?? [], log, hmr ? String(Date.now()) : "");
283
291
  await mountNodes(mods, log);
284
292
  window.scrollTo(0, 0);
285
293
  log.info("\u{1F389} Navigation complete:", href);
@@ -287,6 +295,7 @@ function setupNavigation(log) {
287
295
  log.error("Navigation error, falling back to full reload:", err);
288
296
  window.location.href = href;
289
297
  } finally {
298
+ if (hmr) hmrNavPending = false;
290
299
  clientErrorPending = false;
291
300
  }
292
301
  });
@@ -1,13 +1,25 @@
1
1
  import { log } from "./logger.js";
2
2
  function hmr() {
3
3
  const es = new EventSource("/__hmr");
4
+ let reconnecting = false;
4
5
  es.onopen = () => {
6
+ reconnecting = false;
5
7
  log.info("[HMR] Connected");
6
8
  };
7
9
  es.onerror = () => {
10
+ if (reconnecting) return;
11
+ reconnecting = true;
8
12
  es.close();
9
13
  waitForReconnect();
10
14
  };
15
+ document.addEventListener("visibilitychange", () => {
16
+ if (document.visibilityState !== "visible") return;
17
+ if (es.readyState === EventSource.OPEN) return;
18
+ if (reconnecting) return;
19
+ reconnecting = true;
20
+ es.close();
21
+ waitForReconnect(500, 20);
22
+ });
11
23
  es.onmessage = async (event) => {
12
24
  try {
13
25
  const msg = JSON.parse(event.data);
@@ -48,8 +60,18 @@ function hmr() {
48
60
  }
49
61
  };
50
62
  }
63
+ let _navTimer = null;
64
+ let _navHref = null;
51
65
  function navigate(href) {
52
- window.dispatchEvent(new CustomEvent("locationchange", { detail: { href, hmr: true } }));
66
+ _navHref = href;
67
+ if (_navTimer) clearTimeout(_navTimer);
68
+ _navTimer = setTimeout(() => {
69
+ _navTimer = null;
70
+ if (_navHref !== null) {
71
+ window.dispatchEvent(new CustomEvent("locationchange", { detail: { href: _navHref, hmr: true } }));
72
+ _navHref = null;
73
+ }
74
+ }, 50);
53
75
  }
54
76
  function patternMatchesPathname(pattern, pathname) {
55
77
  const normPattern = pattern.length > 1 ? pattern.replace(/\/+$/, "") : pattern;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export type { TitleValue, HtmlAttrs, BodyAttrs, MetaTag, LinkTag, ScriptTag, Sty
4
4
  export { default as useRouter } from './use-router';
5
5
  export { useRequest } from './use-request';
6
6
  export type { RequestContext } from './use-request';
7
- export { normaliseHeaders, sanitiseHeaders } from './request-store';
7
+ export { normaliseHeaders, sanitiseHeaders, getRequestStore } from './request-store';
8
8
  export { default as Link } from './Link';
9
9
  export { setupLocationChangeMonitor, initRuntime } from './bundle';
10
10
  export type { RuntimeData } from './bundle';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useHtml } from "./use-html.js";
2
2
  import { default as default2 } from "./use-router.js";
3
3
  import { useRequest } from "./use-request.js";
4
- import { normaliseHeaders, sanitiseHeaders } from "./request-store.js";
4
+ import { normaliseHeaders, sanitiseHeaders, getRequestStore } from "./request-store.js";
5
5
  import { default as default3 } from "./Link.js";
6
6
  import { setupLocationChangeMonitor, initRuntime } from "./bundle.js";
7
7
  import { escapeHtml } from "./utils.js";
@@ -12,6 +12,7 @@ export {
12
12
  c,
13
13
  escapeHtml,
14
14
  getDebugLevel,
15
+ getRequestStore,
15
16
  initRuntime,
16
17
  log,
17
18
  normaliseHeaders,
package/dist/renderer.js CHANGED
@@ -20,7 +20,7 @@ function buildWrapperAttrString(attrs) {
20
20
  const parts = Object.entries(attrs).map(([key, value]) => {
21
21
  if (key === "className") key = "class";
22
22
  if (key === "style" && typeof value === "object") {
23
- const css = Object.entries(value).map(([k, v]) => {
23
+ const css = "display:contents;" + Object.entries(value).map(([k, v]) => {
24
24
  const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
25
25
  const safeVal = String(v).replace(/[<>"'`\\]/g, "");
26
26
  return `${prop}:${safeVal}`;
@@ -31,6 +31,7 @@ function buildWrapperAttrString(attrs) {
31
31
  if (value == null) return "";
32
32
  return `${key}="${escapeHtml(String(value))}"`;
33
33
  }).filter(Boolean);
34
+ if (!("style" in attrs)) parts.push('style="display:contents"');
34
35
  return parts.length ? " " + parts.join(" ") : "";
35
36
  }
36
37
  async function renderElementToHtml(element, ctx) {
@@ -67,13 +67,9 @@ export declare function normaliseHeaders(raw: Record<string, string | string[] |
67
67
  export declare function sanitiseHeaders(raw: Record<string, string | string[] | undefined>): Record<string, string>;
68
68
  /**
69
69
  * Runs `fn` inside the context of the given request, then clears the store.
70
- *
71
- * Usage in the SSR pipeline:
72
- * ```ts
73
- * const store = await runWithRequestStore(ctx, async () => {
74
- * appHtml = await renderElementToHtml(element, renderCtx);
75
- * });
76
- * ```
70
+ * The store is set synchronously before `fn` is called, so any code that
71
+ * reads getRequestStore() during the synchronous phase of a server component
72
+ * (before its first `await`) will always see the correct context.
77
73
  */
78
74
  export declare function runWithRequestStore<T>(ctx: RequestContext, fn: () => Promise<T>): Promise<T>;
79
75
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukejs",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
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",