nukejs 0.0.5 → 0.0.7

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 (42) hide show
  1. package/README.md +96 -8
  2. package/dist/Link.js +16 -0
  3. package/dist/Link.js.map +7 -0
  4. package/dist/build-common.d.ts +57 -80
  5. package/dist/build-common.js +156 -168
  6. package/dist/build-common.js.map +2 -2
  7. package/dist/build-node.d.ts +14 -0
  8. package/dist/build-node.js +50 -51
  9. package/dist/build-node.js.map +2 -2
  10. package/dist/build-vercel.d.ts +18 -0
  11. package/dist/build-vercel.js +76 -63
  12. package/dist/build-vercel.js.map +2 -2
  13. package/dist/builder.d.ts +10 -0
  14. package/dist/builder.js +31 -62
  15. package/dist/builder.js.map +3 -3
  16. package/dist/bundle.js +69 -6
  17. package/dist/bundle.js.map +2 -2
  18. package/dist/component-analyzer.d.ts +13 -10
  19. package/dist/component-analyzer.js +26 -17
  20. package/dist/component-analyzer.js.map +2 -2
  21. package/dist/hmr-bundle.js +17 -4
  22. package/dist/hmr-bundle.js.map +2 -2
  23. package/dist/html-store.d.ts +7 -0
  24. package/dist/html-store.js.map +2 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +2 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/renderer.js +2 -7
  29. package/dist/renderer.js.map +2 -2
  30. package/dist/router.js +16 -4
  31. package/dist/router.js.map +2 -2
  32. package/dist/ssr.js +21 -4
  33. package/dist/ssr.js.map +2 -2
  34. package/dist/use-html.js +5 -1
  35. package/dist/use-html.js.map +2 -2
  36. package/dist/use-router.js +28 -0
  37. package/dist/use-router.js.map +7 -0
  38. package/package.json +1 -1
  39. package/dist/as-is/Link.tsx +0 -20
  40. package/dist/as-is/useRouter.ts +0 -33
  41. /package/dist/{as-is/Link.d.ts → Link.d.ts} +0 -0
  42. /package/dist/{as-is/useRouter.d.ts → use-router.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/use-html.ts"],
4
- "sourcesContent": ["/**\r\n * use-html.ts \u2014 useHtml() Hook\r\n *\r\n * A universal hook that lets React components control the HTML document's\r\n * <head>, <html> attributes, and <body> attributes from within JSX \u2014 on both\r\n * the server (SSR) and the client (hydration / SPA navigation).\r\n *\r\n * Server behaviour:\r\n * Writes directly into the per-request html-store. The store is flushed\r\n * into the HTML document after the component tree is fully rendered.\r\n * useHtml() is called synchronously during rendering so no actual React\r\n * hook is used \u2014 it's just a function that pokes the globalThis store.\r\n *\r\n * Client behaviour:\r\n * Uses useEffect() to apply changes to the live document and clean them up\r\n * when the component unmounts (navigation, unmount). Each effect is keyed\r\n * to its options object via JSON.stringify so React re-runs it when the\r\n * options change.\r\n *\r\n * Layout title templates:\r\n * Layouts typically set title as a function so they can append a site suffix:\r\n *\r\n * ```tsx\r\n * // Root layout\r\n * useHtml({ title: (prev) => `${prev} | Acme` });\r\n *\r\n * // A page\r\n * useHtml({ title: 'About' });\r\n * // \u2192 'About | Acme'\r\n * ```\r\n *\r\n * Example usage:\r\n * ```tsx\r\n * useHtml({\r\n * title: 'Blog Post',\r\n * meta: [{ name: 'description', content: 'A great post' }],\r\n * link: [{ rel: 'canonical', href: 'https://example.com/post' }],\r\n * });\r\n * ```\r\n */\r\n\r\nimport { useEffect } from 'react';\r\nimport { getHtmlStore } from './html-store';\r\nimport type {\r\n TitleValue,\r\n HtmlAttrs,\r\n BodyAttrs,\r\n MetaTag,\r\n LinkTag,\r\n ScriptTag,\r\n StyleTag,\r\n} from './html-store';\r\n\r\n// Re-export types so consumers can import them from 'nukejs' directly.\r\nexport type { TitleValue, HtmlAttrs, BodyAttrs, MetaTag, LinkTag, ScriptTag, StyleTag };\r\n\r\n// \u2500\u2500\u2500 Options type \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nexport interface HtmlOptions {\r\n /**\r\n * Page title.\r\n * string \u2192 sets the title directly (page wins over layout).\r\n * function \u2192 receives the inner title; use in layouts to append a suffix:\r\n * `(prev) => \\`${prev} | MySite\\``\r\n */\r\n title?: TitleValue;\r\n /** Attributes merged onto <html>. Per-attribute last-write-wins. */\r\n htmlAttrs?: HtmlAttrs;\r\n /** Attributes merged onto <body>. Per-attribute last-write-wins. */\r\n bodyAttrs?: BodyAttrs;\r\n meta?: MetaTag[];\r\n link?: LinkTag[];\r\n script?: ScriptTag[];\r\n style?: StyleTag[];\r\n}\r\n\r\n// \u2500\u2500\u2500 Universal hook \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Applies HTML document customisations from a React component.\r\n * Automatically detects whether it is running on the server or the client.\r\n */\r\nexport function useHtml(options: HtmlOptions): void {\r\n if (typeof document === 'undefined') {\r\n // Running on the server (SSR) \u2014 write synchronously to the request store.\r\n serverUseHtml(options);\r\n } else {\r\n // Running in the browser \u2014 use React effects.\r\n clientUseHtml(options);\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Server implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Writes options directly into the active per-request html-store.\r\n * Called synchronously during SSR; no React hooks are used.\r\n *\r\n * Title operations are *pushed* (not replaced) so both layout and page values\r\n * are preserved for resolveTitle() to process in the correct order.\r\n */\r\nfunction serverUseHtml(options: HtmlOptions): void {\r\n const store = getHtmlStore();\r\n if (!store) return; // Called outside of a runWithHtmlStore context \u2014 ignore.\r\n\r\n if (options.title !== undefined) store.titleOps.push(options.title);\r\n if (options.htmlAttrs) Object.assign(store.htmlAttrs, options.htmlAttrs);\r\n if (options.bodyAttrs) Object.assign(store.bodyAttrs, options.bodyAttrs);\r\n if (options.meta?.length) store.meta.push(...options.meta);\r\n if (options.link?.length) store.link.push(...options.link);\r\n if (options.script?.length) store.script.push(...options.script);\r\n if (options.style?.length) store.style.push(...options.style);\r\n}\r\n\r\n// \u2500\u2500\u2500 Client implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** Monotonically incrementing counter for generating unique dataset IDs. */\r\nlet _uid = 0;\r\nconst uid = () => `uh${++_uid}`;\r\n\r\n/**\r\n * Applies options to the live document using React effects.\r\n * Each effect type is independent so a change to `title` does not re-run the\r\n * `meta` effect and vice versa.\r\n *\r\n * Cleanup functions restore the previous state so unmounting a component that\r\n * called useHtml() reverses its changes (important for SPA navigation).\r\n */\r\nfunction clientUseHtml(options: HtmlOptions): void {\r\n // \u2500\u2500 title \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (options.title === undefined) return;\r\n const prev = document.title;\r\n document.title = typeof options.title === 'function'\r\n ? options.title(prev)\r\n : options.title;\r\n return () => { document.title = prev; };\r\n }, [typeof options.title === 'function' // eslint-disable-line react-hooks/exhaustive-deps\r\n ? options.title.toString()\r\n : options.title]);\r\n\r\n // \u2500\u2500 <html> attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.htmlAttrs) return;\r\n return applyAttrs(document.documentElement, options.htmlAttrs);\r\n }, [JSON.stringify(options.htmlAttrs)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <body> attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.bodyAttrs) return;\r\n return applyAttrs(document.body, options.bodyAttrs);\r\n }, [JSON.stringify(options.bodyAttrs)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <meta> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.meta?.length) return;\r\n const id = uid();\r\n const nodes = options.meta.map((tag) => {\r\n const el = document.createElement('meta');\r\n for (const [k, v] of Object.entries(tag)) {\r\n if (v !== undefined) el.setAttribute(domAttr(k), v);\r\n }\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.meta)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <link> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.link?.length) return;\r\n const id = uid();\r\n const nodes = options.link.map((tag) => {\r\n const el = document.createElement('link');\r\n for (const [k, v] of Object.entries(tag)) {\r\n if (v !== undefined) el.setAttribute(domAttr(k), v);\r\n }\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.link)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <script> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.script?.length) return;\r\n const id = uid();\r\n const nodes = options.script.map((tag) => {\r\n const el = document.createElement('script');\r\n if (tag.src) el.src = tag.src;\r\n if (tag.type) el.type = tag.type;\r\n if (tag.defer) el.defer = true;\r\n if (tag.async) el.async = true;\r\n if (tag.noModule) el.setAttribute('nomodule', '');\r\n if (tag.crossOrigin) el.crossOrigin = tag.crossOrigin;\r\n if (tag.integrity) el.integrity = tag.integrity;\r\n if (tag.content) el.textContent = tag.content;\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.script)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <style> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.style?.length) return;\r\n const id = uid();\r\n const nodes = options.style.map((tag) => {\r\n const el = document.createElement('style');\r\n if (tag.media) el.media = tag.media;\r\n if (tag.content) el.textContent = tag.content;\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.style)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n}\r\n\r\n// \u2500\u2500\u2500 Attribute helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Applies an attribute map to a DOM element, storing the previous values so\r\n * the returned cleanup function can restore them on unmount.\r\n */\r\nfunction applyAttrs(\r\n el: Element,\r\n attrs: Record<string, string | undefined>,\r\n): () => void {\r\n const prev: Record<string, string | null> = {};\r\n for (const [k, v] of Object.entries(attrs)) {\r\n if (v === undefined) continue;\r\n const attr = domAttr(k);\r\n prev[attr] = el.getAttribute(attr);\r\n el.setAttribute(attr, v);\r\n }\r\n return () => {\r\n for (const [attr, was] of Object.entries(prev)) {\r\n if (was === null) el.removeAttribute(attr);\r\n else el.setAttribute(attr, was);\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Converts camelCase React prop names to their HTML attribute equivalents.\r\n * httpEquiv \u2192 http-equiv\r\n * hrefLang \u2192 hreflang\r\n * crossOrigin \u2192 crossorigin\r\n */\r\nfunction domAttr(key: string): string {\r\n if (key === 'httpEquiv') return 'http-equiv';\r\n if (key === 'hrefLang') return 'hreflang';\r\n if (key === 'crossOrigin') return 'crossorigin';\r\n return key;\r\n}\r\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\r\n * use-html.ts \u2014 useHtml() Hook\r\n *\r\n * A universal hook that lets React components control the HTML document's\r\n * <head>, <html> attributes, and <body> attributes from within JSX \u2014 on both\r\n * the server (SSR) and the client (hydration / SPA navigation).\r\n *\r\n * Server behaviour:\r\n * Writes directly into the per-request html-store. The store is flushed\r\n * into the HTML document after the component tree is fully rendered.\r\n * useHtml() is called synchronously during rendering so no actual React\r\n * hook is used \u2014 it's just a function that pokes the globalThis store.\r\n *\r\n * Client behaviour:\r\n * Uses useEffect() to apply changes to the live document and clean them up\r\n * when the component unmounts (navigation, unmount). Each effect is keyed\r\n * to its options object via JSON.stringify so React re-runs it when the\r\n * options change.\r\n *\r\n * Layout title templates:\r\n * Layouts typically set title as a function so they can append a site suffix:\r\n *\r\n * ```tsx\r\n * // Root layout\r\n * useHtml({ title: (prev) => `${prev} | Acme` });\r\n *\r\n * // A page\r\n * useHtml({ title: 'About' });\r\n * // \u2192 'About | Acme'\r\n * ```\r\n *\r\n * Example usage:\r\n * ```tsx\r\n * useHtml({\r\n * title: 'Blog Post',\r\n * meta: [{ name: 'description', content: 'A great post' }],\r\n * link: [{ rel: 'canonical', href: 'https://example.com/post' }],\r\n * });\r\n * ```\r\n */\r\n\r\nimport { useEffect } from 'react';\r\nimport { getHtmlStore } from './html-store';\r\nimport type {\r\n TitleValue,\r\n HtmlAttrs,\r\n BodyAttrs,\r\n MetaTag,\r\n LinkTag,\r\n ScriptTag,\r\n StyleTag,\r\n} from './html-store';\r\n\r\n// Re-export types so consumers can import them from 'nukejs' directly.\r\nexport type { TitleValue, HtmlAttrs, BodyAttrs, MetaTag, LinkTag, ScriptTag, StyleTag };\r\n\r\n// \u2500\u2500\u2500 Options type \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nexport interface HtmlOptions {\r\n /**\r\n * Page title.\r\n * string \u2192 sets the title directly (page wins over layout).\r\n * function \u2192 receives the inner title; use in layouts to append a suffix:\r\n * `(prev) => \\`${prev} | MySite\\``\r\n */\r\n title?: TitleValue;\r\n /** Attributes merged onto <html>. Per-attribute last-write-wins. */\r\n htmlAttrs?: HtmlAttrs;\r\n /** Attributes merged onto <body>. Per-attribute last-write-wins. */\r\n bodyAttrs?: BodyAttrs;\r\n meta?: MetaTag[];\r\n link?: LinkTag[];\r\n script?: ScriptTag[];\r\n style?: StyleTag[];\r\n}\r\n\r\n// \u2500\u2500\u2500 Universal hook \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Applies HTML document customisations from a React component.\r\n * Automatically detects whether it is running on the server or the client.\r\n */\r\nexport function useHtml(options: HtmlOptions): void {\r\n if (typeof document === 'undefined') {\r\n // Running on the server (SSR) \u2014 write synchronously to the request store.\r\n serverUseHtml(options);\r\n } else {\r\n // Running in the browser \u2014 use React effects.\r\n clientUseHtml(options);\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Server implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Writes options directly into the active per-request html-store.\r\n * Called synchronously during SSR; no React hooks are used.\r\n *\r\n * Title operations are *pushed* (not replaced) so both layout and page values\r\n * are preserved for resolveTitle() to process in the correct order.\r\n */\r\nfunction serverUseHtml(options: HtmlOptions): void {\r\n const store = getHtmlStore();\r\n if (!store) return; // Called outside of a runWithHtmlStore context \u2014 ignore.\r\n\r\n if (options.title !== undefined) store.titleOps.push(options.title);\r\n if (options.htmlAttrs) Object.assign(store.htmlAttrs, options.htmlAttrs);\r\n if (options.bodyAttrs) Object.assign(store.bodyAttrs, options.bodyAttrs);\r\n if (options.meta?.length) store.meta.push(...options.meta);\r\n if (options.link?.length) store.link.push(...options.link);\r\n if (options.script?.length) store.script.push(...options.script);\r\n if (options.style?.length) store.style.push(...options.style);\r\n}\r\n\r\n// \u2500\u2500\u2500 Client implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** Monotonically incrementing counter for generating unique dataset IDs. */\r\nlet _uid = 0;\r\nconst uid = () => `uh${++_uid}`;\r\n\r\n/**\r\n * Applies options to the live document using React effects.\r\n * Each effect type is independent so a change to `title` does not re-run the\r\n * `meta` effect and vice versa.\r\n *\r\n * Cleanup functions restore the previous state so unmounting a component that\r\n * called useHtml() reverses its changes (important for SPA navigation).\r\n */\r\nfunction clientUseHtml(options: HtmlOptions): void {\r\n // \u2500\u2500 title \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (options.title === undefined) return;\r\n const prev = document.title;\r\n document.title = typeof options.title === 'function'\r\n ? options.title(prev)\r\n : options.title;\r\n return () => { document.title = prev; };\r\n }, [typeof options.title === 'function' // eslint-disable-line react-hooks/exhaustive-deps\r\n ? options.title.toString()\r\n : options.title]);\r\n\r\n // \u2500\u2500 <html> attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.htmlAttrs) return;\r\n return applyAttrs(document.documentElement, options.htmlAttrs);\r\n }, [JSON.stringify(options.htmlAttrs)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <body> attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.bodyAttrs) return;\r\n return applyAttrs(document.body, options.bodyAttrs);\r\n }, [JSON.stringify(options.bodyAttrs)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <meta> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.meta?.length) return;\r\n const id = uid();\r\n const nodes = options.meta.map((tag) => {\r\n const el = document.createElement('meta');\r\n for (const [k, v] of Object.entries(tag)) {\r\n if (v !== undefined) el.setAttribute(domAttr(k), v);\r\n }\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.meta)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <link> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.link?.length) return;\r\n const id = uid();\r\n const nodes = options.link.map((tag) => {\r\n const el = document.createElement('link');\r\n for (const [k, v] of Object.entries(tag)) {\r\n if (v !== undefined) el.setAttribute(domAttr(k), v);\r\n }\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.link)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <script> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.script?.length) return;\r\n const id = uid();\r\n const nodes = options.script.map((tag) => {\r\n const el = document.createElement('script');\r\n if (tag.src) el.src = tag.src;\r\n if (tag.type) el.type = tag.type;\r\n if (tag.defer) el.defer = true;\r\n if (tag.async) el.async = true;\r\n if (tag.noModule) el.setAttribute('nomodule', '');\r\n if (tag.crossOrigin) el.crossOrigin = tag.crossOrigin;\r\n if (tag.integrity) el.integrity = tag.integrity;\r\n if (tag.content) el.textContent = tag.content;\r\n el.dataset.usehtml = id;\r\n // Respect position: 'body' scripts are appended at the end of <body>.\r\n if (tag.position === 'body') {\r\n document.body.appendChild(el);\r\n } else {\r\n document.head.appendChild(el);\r\n }\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.script)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n\r\n // \u2500\u2500 <style> tags \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 useEffect(() => { // eslint-disable-line react-hooks/rules-of-hooks\r\n if (!options.style?.length) return;\r\n const id = uid();\r\n const nodes = options.style.map((tag) => {\r\n const el = document.createElement('style');\r\n if (tag.media) el.media = tag.media;\r\n if (tag.content) el.textContent = tag.content;\r\n el.dataset.usehtml = id;\r\n document.head.appendChild(el);\r\n return el;\r\n });\r\n return () => nodes.forEach(n => n.remove());\r\n }, [JSON.stringify(options.style)]); // eslint-disable-line react-hooks/exhaustive-deps\r\n}\r\n\r\n// \u2500\u2500\u2500 Attribute helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Applies an attribute map to a DOM element, storing the previous values so\r\n * the returned cleanup function can restore them on unmount.\r\n */\r\nfunction applyAttrs(\r\n el: Element,\r\n attrs: Record<string, string | undefined>,\r\n): () => void {\r\n const prev: Record<string, string | null> = {};\r\n for (const [k, v] of Object.entries(attrs)) {\r\n if (v === undefined) continue;\r\n const attr = domAttr(k);\r\n prev[attr] = el.getAttribute(attr);\r\n el.setAttribute(attr, v);\r\n }\r\n return () => {\r\n for (const [attr, was] of Object.entries(prev)) {\r\n if (was === null) el.removeAttribute(attr);\r\n else el.setAttribute(attr, was);\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Converts camelCase React prop names to their HTML attribute equivalents.\r\n * httpEquiv \u2192 http-equiv\r\n * hrefLang \u2192 hreflang\r\n * crossOrigin \u2192 crossorigin\r\n */\r\nfunction domAttr(key: string): string {\r\n if (key === 'httpEquiv') return 'http-equiv';\r\n if (key === 'hrefLang') return 'hreflang';\r\n if (key === 'crossOrigin') return 'crossorigin';\r\n return key;\r\n}"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,28 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ function useRouter() {
3
+ try {
4
+ const [path, setPath] = useState(() => window.location.pathname);
5
+ useEffect(() => {
6
+ const handleLocationChange = () => setPath(window.location.pathname);
7
+ window.addEventListener("locationchange", handleLocationChange);
8
+ return () => window.removeEventListener("locationchange", handleLocationChange);
9
+ }, []);
10
+ const push = useCallback((url) => {
11
+ window.history.pushState({}, "", url);
12
+ setPath(url);
13
+ }, []);
14
+ const replace = useCallback((url) => {
15
+ window.history.replaceState({}, "", url);
16
+ setPath(url);
17
+ }, []);
18
+ return { path, push, replace };
19
+ } catch {
20
+ return { push: () => {
21
+ }, replace: () => {
22
+ }, path: "" };
23
+ }
24
+ }
25
+ export {
26
+ useRouter as default
27
+ };
28
+ //# sourceMappingURL=use-router.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/use-router.ts"],
4
+ "sourcesContent": ["import { useCallback, useEffect, useState } from \"react\";\r\n\r\ntype Router = {\r\n path: string;\r\n push: (url: string) => void;\r\n replace: (url: string) => void;\r\n};\r\n\r\nexport default function useRouter(): Router {\r\n try {\r\n const [path, setPath] = useState(() => window.location.pathname);\r\n\r\n useEffect(() => {\r\n const handleLocationChange = () => setPath(window.location.pathname);\r\n window.addEventListener(\"locationchange\", handleLocationChange);\r\n return () => window.removeEventListener(\"locationchange\", handleLocationChange);\r\n }, []);\r\n\r\n const push = useCallback((url: string) => {\r\n window.history.pushState({}, \"\", url);\r\n setPath(url);\r\n }, []);\r\n\r\n const replace = useCallback((url: string) => {\r\n window.history.replaceState({}, \"\", url);\r\n setPath(url);\r\n }, []);\r\n\r\n return { path, push, replace };\r\n } catch {\r\n return { push: () => {}, replace: () => {}, path: \"\" };\r\n }\r\n}"],
5
+ "mappings": "AAAA,SAAS,aAAa,WAAW,gBAAgB;AAQlC,SAAR,YAAqC;AACxC,MAAI;AACA,UAAM,CAAC,MAAM,OAAO,IAAI,SAAS,MAAM,OAAO,SAAS,QAAQ;AAE/D,cAAU,MAAM;AACZ,YAAM,uBAAuB,MAAM,QAAQ,OAAO,SAAS,QAAQ;AACnE,aAAO,iBAAiB,kBAAkB,oBAAoB;AAC9D,aAAO,MAAM,OAAO,oBAAoB,kBAAkB,oBAAoB;AAAA,IAClF,GAAG,CAAC,CAAC;AAEL,UAAM,OAAO,YAAY,CAAC,QAAgB;AACtC,aAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,GAAG;AACpC,cAAQ,GAAG;AAAA,IACf,GAAG,CAAC,CAAC;AAEL,UAAM,UAAU,YAAY,CAAC,QAAgB;AACzC,aAAO,QAAQ,aAAa,CAAC,GAAG,IAAI,GAAG;AACvC,cAAQ,GAAG;AAAA,IACf,GAAG,CAAC,CAAC;AAEL,WAAO,EAAE,MAAM,MAAM,QAAQ;AAAA,EACjC,QAAQ;AACJ,WAAO,EAAE,MAAM,MAAM;AAAA,IAAC,GAAG,SAAS,MAAM;AAAA,IAAC,GAAG,MAAM,GAAG;AAAA,EACzD;AACJ;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukejs",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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",
@@ -1,20 +0,0 @@
1
- "use client"
2
-
3
- const Link = ({ href, children, className }: {
4
- href: string;
5
- children: React.ReactNode;
6
- className?: string;
7
- }) => {
8
- const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
9
- e.preventDefault();
10
- window.history.pushState({}, '', href);
11
- };
12
-
13
- return (
14
- <a href={href} onClick={handleClick} className={className}>
15
- {children}
16
- </a>
17
- );
18
- };
19
-
20
- export default Link;
@@ -1,33 +0,0 @@
1
- import { useCallback, useEffect, useState } from "react";
2
-
3
- type Router = {
4
- path: string;
5
- push: (url: string) => void;
6
- replace: (url: string) => void;
7
- };
8
-
9
- export default function useRouter(): Router {
10
- try {
11
- const [path, setPath] = useState(() => window.location.pathname);
12
-
13
- useEffect(() => {
14
- const handleLocationChange = () => setPath(window.location.pathname);
15
- window.addEventListener("locationchange", handleLocationChange);
16
- return () => window.removeEventListener("locationchange", handleLocationChange);
17
- }, []);
18
-
19
- const push = useCallback((url: string) => {
20
- window.history.pushState({}, "", url);
21
- setPath(url);
22
- }, []);
23
-
24
- const replace = useCallback((url: string) => {
25
- window.history.replaceState({}, "", url);
26
- setPath(url);
27
- }, []);
28
-
29
- return { path, push, replace };
30
- } catch {
31
- return { push: () => {}, replace: () => {}, path: "" };
32
- }
33
- }
File without changes
File without changes