routerino 2.2.2-rc2 → 2.2.2-rc4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/routerino.js CHANGED
@@ -1,9 +1,9 @@
1
- import { jsx as g, jsxs as D, Fragment as O } from "react/jsx-runtime";
2
- import { createContext as G, Component as K, useContext as J, useState as Q, useEffect as V } from "react";
1
+ import { jsx as g, jsxs as j, Fragment as M } from "react/jsx-runtime";
2
+ import { createContext as J, Component as Q, useContext as V, useState as X, useEffect as Y } from "react";
3
3
  import t from "prop-types";
4
- const j = G(null);
5
- function oe() {
6
- const i = J(j);
4
+ const _ = J(null);
5
+ function ne() {
6
+ const i = V(_);
7
7
  if (!i)
8
8
  throw new Error(
9
9
  "useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component."
@@ -23,13 +23,13 @@ function l({ tag: i = "meta", soft: c = !1, ...a }) {
23
23
  ;
24
24
  h && c || (h || (h = document.createElement(i)), o.forEach((f) => h.setAttribute(f, a[f])), document.querySelector("head").appendChild(h));
25
25
  }
26
- function X({ routePattern: i, currentRoute: c }) {
26
+ function Z({ routePattern: i, currentRoute: c }) {
27
27
  let a = {}, o = i.split("/"), h = c.split("/");
28
28
  return o.forEach((f, m) => {
29
29
  f.startsWith(":") && (a[f.slice(1)] = h[m]);
30
30
  }), a;
31
31
  }
32
- class M extends K {
32
+ class z extends Q {
33
33
  constructor(c) {
34
34
  super(c), this.state = { hasError: !1 };
35
35
  }
@@ -48,7 +48,7 @@ class M extends K {
48
48
  return this.state.hasError ? this.props.fallback : this.props.children;
49
49
  }
50
50
  }
51
- M.propTypes = {
51
+ z.propTypes = {
52
52
  /** The child components to render when there's no error */
53
53
  children: t.node,
54
54
  /** The fallback UI to display when an error is caught */
@@ -62,7 +62,7 @@ M.propTypes = {
62
62
  /** Whether to log debug messages to console (optional) */
63
63
  debug: t.bool
64
64
  };
65
- function Y({
65
+ function N({
66
66
  routes: i = [
67
67
  {
68
68
  path: "/",
@@ -72,26 +72,26 @@ function Y({
72
72
  tags: [{ property: "og:locale", content: "en_US" }]
73
73
  }
74
74
  ],
75
- notFoundTemplate: c = /* @__PURE__ */ D(O, { children: [
75
+ notFoundTemplate: c = /* @__PURE__ */ j(M, { children: [
76
76
  /* @__PURE__ */ g("p", { children: "No page found for this URL. [404]" }),
77
77
  /* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
78
78
  ] }),
79
79
  notFoundTitle: a = "Page not found [404]",
80
- errorTemplate: o = /* @__PURE__ */ D(O, { children: [
80
+ errorTemplate: o = /* @__PURE__ */ j(M, { children: [
81
81
  /* @__PURE__ */ g("p", { children: "Page failed to load. [500]" }),
82
82
  /* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
83
83
  ] }),
84
84
  errorTitle: h = "Page error [500]",
85
85
  useTrailingSlash: f = !0,
86
86
  usePrerenderTags: m = !1,
87
- baseUrl: _ = null,
87
+ baseUrl: G = null,
88
88
  title: R = "",
89
89
  separator: y = " | ",
90
90
  imageUrl: v = null,
91
91
  touchIconUrl: T = null,
92
92
  debug: s = !1
93
93
  }) {
94
- var P, U, C, L, q;
94
+ var P, U, C, L, q, B, W;
95
95
  const k = `${h}${y}${R}`, x = `${a}${y}${R}`;
96
96
  try {
97
97
  if (s) {
@@ -109,8 +109,10 @@ function Y({
109
109
  ""
110
110
  ));
111
111
  }
112
- const [$, B] = Q(window.location.href);
113
- V(() => {
112
+ const [$, A] = X(((P = window == null ? void 0 : window.location) == null ? void 0 : P.href) ?? "/");
113
+ Y(() => {
114
+ if (typeof window > "u" || typeof document > "u")
115
+ return;
114
116
  const e = (w) => {
115
117
  s && console.debug(
116
118
  "%c[Routerino]%c click occurred",
@@ -176,7 +178,7 @@ function Y({
176
178
  "%c[Routerino]%c target link is same origin, will use push-state transitioning",
177
179
  "color: #6b7280; font-weight: bold",
178
180
  ""
179
- ), w.preventDefault(), d.href !== window.location.href && (B(d.href), window.history.pushState({}, "", d.href)), window.scrollTo({
181
+ ), w.preventDefault(), d.href !== window.location.href && (A(d.href), window.history.pushState({}, "", d.href)), window.scrollTo({
180
182
  top: 0,
181
183
  behavior: "auto"
182
184
  })) : s && console.debug(
@@ -192,25 +194,25 @@ function Y({
192
194
  "color: #6b7280; font-weight: bold",
193
195
  "",
194
196
  window.location.pathname
195
- ), B(window.location.href);
197
+ ), A(window.location.href);
196
198
  };
197
199
  return window.addEventListener("popstate", u), () => {
198
200
  document.removeEventListener("click", e), window.removeEventListener("popstate", u);
199
201
  };
200
202
  }, [$]);
201
- let r = ((P = window.location) == null ? void 0 : P.pathname) ?? "/";
203
+ let r = ((U = window == null ? void 0 : window.location) == null ? void 0 : U.pathname) ?? "/";
202
204
  (r === "/index.html" || r === "") && (r = "/");
203
- const W = i.find((e) => e.path === r), A = i.find(
205
+ const F = i.find((e) => e.path === r), H = i.find(
204
206
  (e) => `${e.path}/` === r || e.path === `${r}/`
205
- ), F = i.find((e) => {
207
+ ), I = i.find((e) => {
206
208
  const u = e.path.endsWith("/") ? e.path.slice(0, -1) : e.path, w = r.endsWith("/") ? r.slice(0, -1) : r, d = u.split("/").filter(Boolean), p = w.split("/").filter(Boolean);
207
209
  return d.length !== p.length ? !1 : d.every((b, S) => b.startsWith(":") ? !0 : b === p[S]);
208
- }), n = W ?? A ?? F;
210
+ }), n = F ?? H ?? I;
209
211
  if (s && console.debug(
210
212
  "%c[Routerino]%c Route matching:",
211
213
  "color: #6b7280; font-weight: bold",
212
214
  "",
213
- { match: n, exactMatch: W, addSlashMatch: A, paramsMatch: F }
215
+ { match: n, exactMatch: F, addSlashMatch: H, paramsMatch: I }
214
216
  ), !n)
215
217
  return s && (console.group(
216
218
  "%c[Routerino]%c 404 - No matching route",
@@ -237,39 +239,39 @@ function Y({
237
239
  );
238
240
  u && u.remove();
239
241
  }
240
- const H = f && !r.endsWith("/") && r !== "/", I = !f && r.endsWith("/") && r !== "/", z = H ? `${r}/` : I ? r.slice(0, -1) : r, E = `${_ || window.location.origin}${z}`;
242
+ const D = f && !r.endsWith("/") && r !== "/", O = !f && r.endsWith("/") && r !== "/", K = D ? `${r}/` : O ? r.slice(0, -1) : r, E = `${G ?? ((C = window == null ? void 0 : window.location) == null ? void 0 : C.origin) ?? ""}${K}`;
241
243
  if (n.title) {
242
244
  const e = `${n.title}${y}${R}`;
243
245
  document.title = e, l({
244
246
  tag: "link",
245
247
  rel: "canonical",
246
248
  href: E
247
- }), (U = n.tags) != null && U.find(({ property: u }) => u === "og:title") || l({
249
+ }), (L = n.tags) != null && L.find(({ property: u }) => u === "og:title") || l({
248
250
  property: "og:title",
249
251
  content: e
250
- }), (C = n.tags) != null && C.find(({ property: u }) => u === "og:url") || l({
252
+ }), (q = n.tags) != null && q.find(({ property: u }) => u === "og:url") || l({
251
253
  property: "og:url",
252
254
  content: E
253
255
  });
254
256
  }
255
- if (n.description && (l({ name: "description", content: n.description }), (L = n.tags) != null && L.find(({ property: e }) => e === "og:description") || l({
257
+ if (n.description && (l({ name: "description", content: n.description }), (B = n.tags) != null && B.find(({ property: e }) => e === "og:description") || l({
256
258
  property: "og:description",
257
259
  content: n.description
258
260
  })), (v || n.imageUrl) && l({
259
261
  property: "og:image",
260
262
  content: n.imageUrl ?? v
261
- }), (q = n.tags) != null && q.find(({ property: e }) => e === "twitter:card") || l({
263
+ }), (W = n.tags) != null && W.find(({ property: e }) => e === "twitter:card") || l({
262
264
  name: "twitter:card",
263
265
  content: "summary_large_image"
264
266
  }), T && l({
265
267
  tag: "link",
266
268
  rel: "apple-touch-icon",
267
269
  href: T
268
- }), m && (H || I) && (l({ name: "prerender-status-code", content: "301" }), l({
270
+ }), m && (D || O) && (l({ name: "prerender-status-code", content: "301" }), l({
269
271
  name: "prerender-header",
270
272
  content: `Location: ${E}`
271
273
  })), n.tags && n.tags.length ? (n.tags.find(({ property: e }) => e === "og:type") || l({ property: "og:type", content: "website" }), n.tags.forEach((e) => l(e))) : l({ property: "og:type", content: "website" }), n.element) {
272
- const e = X({
274
+ const e = Z({
273
275
  routePattern: n.path,
274
276
  currentRoute: r
275
277
  }), u = {
@@ -278,8 +280,8 @@ function Y({
278
280
  routePattern: n.path,
279
281
  updateHeadTag: l
280
282
  };
281
- return /* @__PURE__ */ g(j.Provider, { value: u, children: /* @__PURE__ */ g(
282
- M,
283
+ return /* @__PURE__ */ g(_.Provider, { value: u, children: /* @__PURE__ */ g(
284
+ z,
283
285
  {
284
286
  fallback: o,
285
287
  errorTitleString: k,
@@ -317,7 +319,7 @@ function Y({
317
319
  ), console.groupEnd()), m && l({ name: "prerender-status-code", content: "500" }), document.title = k, o;
318
320
  }
319
321
  }
320
- const Z = t.exact({
322
+ const ee = t.exact({
321
323
  path: (i, c, a) => {
322
324
  const o = i[c];
323
325
  return o == null ? new Error(
@@ -334,8 +336,8 @@ const Z = t.exact({
334
336
  tags: t.arrayOf(t.object),
335
337
  imageUrl: t.string
336
338
  });
337
- Y.propTypes = {
338
- routes: t.arrayOf(Z),
339
+ N.propTypes = {
340
+ routes: t.arrayOf(ee),
339
341
  title: t.string,
340
342
  separator: t.string,
341
343
  notFoundTemplate: t.element,
@@ -363,9 +365,9 @@ Y.propTypes = {
363
365
  debug: t.bool
364
366
  };
365
367
  export {
366
- M as ErrorBoundary,
367
- Y as Routerino,
368
- Y as default,
368
+ z as ErrorBoundary,
369
+ N as Routerino,
370
+ N as default,
369
371
  l as updateHeadTag,
370
- oe as useRouterino
372
+ ne as useRouterino
371
373
  };
@@ -1 +1 @@
1
- (function(g,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],a):(g=typeof globalThis<"u"?globalThis:g||self,a(g.routerino={},g["react/jsx-runtime"],g.React,g.PropTypes))})(this,function(g,a,$,t){"use strict";const q=$.createContext(null);function G(){const c=$.useContext(q);if(!c)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return c}function i({tag:c="meta",soft:l=!1,...s}){const o=Object.keys(s);if(o.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${c} tag`);let p=null;for(let h=0;h<o.length&&(o[h]!=="content"&&(p=document.querySelector(`${c}[${o[h]}='${s[o[h]]}']`)),!p);h++);p&&l||(p||(p=document.createElement(c)),o.forEach(h=>p.setAttribute(h,s[h])),document.querySelector("head").appendChild(p))}function K({routePattern:c,currentRoute:l}){let s={},o=c.split("/"),p=l.split("/");return o.forEach((h,b)=>{h.startsWith(":")&&(s[h.slice(1)]=p[b])}),s}class E extends $.Component{constructor(l){super(l),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(l,s){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",l),console.error("[Routerino] Component Stack:",s.componentStack),this.props.routePath&&console.error("[Routerino] Failed Route:",this.props.routePath),console.error("[Routerino] Error occurred at:",new Date().toISOString()),console.groupEnd()),document.title=this.props.errorTitleString,this.props.usePrerenderTags&&i({name:"prerender-status-code",content:"500"})}render(){return this.state.hasError?this.props.fallback:this.props.children}}E.propTypes={children:t.node,fallback:t.node,errorTitleString:t.string.isRequired,usePrerenderTags:t.bool,routePath:t.string,debug:t.bool};function S({routes:c=[{path:"/",element:a.jsx("p",{children:"This is the default route. Pass an array of routes to the Routerino component in order to configure your own pages. Each route is a dictionary with at least `path` and `element` defined."}),title:"Routerino default route example",description:"The default route example description.",tags:[{property:"og:locale",content:"en_US"}]}],notFoundTemplate:l=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"No page found for this URL. [404]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:s="Page not found [404]",errorTemplate:o=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"Page failed to load. [500]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:p="Page error [500]",useTrailingSlash:h=!0,usePrerenderTags:b=!1,baseUrl:Q=null,title:v="",separator:y=" | ",imageUrl:C=null,touchIconUrl:B=null,debug:u=!1}){var F,H,j,A,I;const L=`${p}${y}${v}`,W=`${s}${y}${v}`;try{if(u){const e=c.map(m=>m.path),d=e.filter((m,f)=>e.indexOf(m)!==f);d.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(d)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[k,M]=$.useState(window.location.href);$.useEffect(()=>{const e=m=>{u&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let f=m.target;for(;f.tagName!=="A"&&f.parentElement;)f=f.parentElement;if(f.tagName!=="A"){u&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const w=f.getAttribute("href")||f.href;if(!w){u&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(w)){u&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",w);return}u&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",w);let R;try{R=new URL(w,window.location.href)}catch(U){u&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",w,U);return}u&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",R,"current:",window.location),R&&window.location.origin===R.origin?(u&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),m.preventDefault(),f.href!==window.location.href&&(M(f.href),window.history.pushState({},"",f.href)),window.scrollTo({top:0,behavior:"auto"})):u&&console.debug("%c[Routerino]%c target link does not share an origin, standard browser link handling applies","color: #6b7280; font-weight: bold","")};document.addEventListener("click",e);const d=()=>{u&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),M(window.location.href)};return window.addEventListener("popstate",d),()=>{document.removeEventListener("click",e),window.removeEventListener("popstate",d)}},[k]);let r=((F=window.location)==null?void 0:F.pathname)??"/";(r==="/index.html"||r==="")&&(r="/");const O=c.find(e=>e.path===r),D=c.find(e=>`${e.path}/`===r||e.path===`${r}/`),_=c.find(e=>{const d=e.path.endsWith("/")?e.path.slice(0,-1):e.path,m=r.endsWith("/")?r.slice(0,-1):r,f=d.split("/").filter(Boolean),w=m.split("/").filter(Boolean);return f.length!==w.length?!1:f.every((R,U)=>R.startsWith(":")?!0:R===w[U])}),n=O??D??_;if(u&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:n,exactMatch:O,addSlashMatch:D,paramsMatch:_}),!n)return u&&(console.group("%c[Routerino]%c 404 - No matching route","color: #f59e0b; font-weight: bold",""),console.warn("%c[Routerino]%c Requested path:","color: #f59e0b; font-weight: bold","",r),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",c.map(e=>e.path)),console.groupEnd()),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l;if(b){const e=document.querySelector('meta[name="prerender-status-code"]');e&&e.remove();const d=document.querySelector('meta[name="prerender-header"]');d&&d.remove()}const z=h&&!r.endsWith("/")&&r!=="/",T=!h&&r.endsWith("/")&&r!=="/",V=z?`${r}/`:T?r.slice(0,-1):r,x=`${Q||window.location.origin}${V}`;if(n.title){const e=`${n.title}${y}${v}`;document.title=e,i({tag:"link",rel:"canonical",href:x}),(H=n.tags)!=null&&H.find(({property:d})=>d==="og:title")||i({property:"og:title",content:e}),(j=n.tags)!=null&&j.find(({property:d})=>d==="og:url")||i({property:"og:url",content:x})}if(n.description&&(i({name:"description",content:n.description}),(A=n.tags)!=null&&A.find(({property:e})=>e==="og:description")||i({property:"og:description",content:n.description})),(C||n.imageUrl)&&i({property:"og:image",content:n.imageUrl??C}),(I=n.tags)!=null&&I.find(({property:e})=>e==="twitter:card")||i({name:"twitter:card",content:"summary_large_image"}),B&&i({tag:"link",rel:"apple-touch-icon",href:B}),b&&(z||T)&&(i({name:"prerender-status-code",content:"301"}),i({name:"prerender-header",content:`Location: ${x}`})),n.tags&&n.tags.length?(n.tags.find(({property:e})=>e==="og:type")||i({property:"og:type",content:"website"}),n.tags.forEach(e=>i(e))):i({property:"og:type",content:"website"}),n.element){const e=K({routePattern:n.path,currentRoute:r}),d={currentRoute:r,params:e,routePattern:n.path,updateHeadTag:i};return a.jsx(q.Provider,{value:d,children:a.jsx(E,{fallback:o,errorTitleString:L,usePrerenderTags:b,routePath:r,debug:u,children:n.element})})}return u&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",r),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l}catch(k){return u&&(console.group("%c[Routerino]%c Fatal Error","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c An error occurred in the router itself (not in a route component)","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c Error:","color: #ff6b6b; font-weight: bold","",k),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),b&&i({name:"prerender-status-code",content:"500"}),document.title=L,o}}const J=t.exact({path:(c,l,s)=>{const o=c[l];return o==null?new Error(`The prop \`${l}\` is marked as required in \`${s}\`, but its value is \`${o}\`.`):typeof o!="string"?new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`):o.startsWith("/")?null:new Error(`Invalid prop \`${l}\` value \`${o}\` supplied to \`${s}\`. Route paths must start with a forward slash (/).`)},element:t.element.isRequired,title:t.string,description:t.string,tags:t.arrayOf(t.object),imageUrl:t.string});S.propTypes={routes:t.arrayOf(J),title:t.string,separator:t.string,notFoundTemplate:t.element,notFoundTitle:t.string,errorTemplate:t.element,errorTitle:t.string,useTrailingSlash:t.bool,usePrerenderTags:t.bool,baseUrl:(c,l,s)=>{const o=c[l];if(o!=null){if(typeof o!="string")return new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`);if(o.endsWith("/"))return new Error(`Invalid prop \`${l}\` supplied to \`${s}\`. The baseUrl should not end with a slash. Got: "${o}"`)}return null},imageUrl:t.string,touchIconUrl:t.string,debug:t.bool},g.ErrorBoundary=E,g.Routerino=S,g.default=S,g.updateHeadTag=i,g.useRouterino=G,Object.defineProperties(g,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(g,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],a):(g=typeof globalThis<"u"?globalThis:g||self,a(g.routerino={},g["react/jsx-runtime"],g.React,g.PropTypes))})(this,function(g,a,$,t){"use strict";const q=$.createContext(null);function J(){const c=$.useContext(q);if(!c)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return c}function i({tag:c="meta",soft:l=!1,...s}){const o=Object.keys(s);if(o.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${c} tag`);let p=null;for(let h=0;h<o.length&&(o[h]!=="content"&&(p=document.querySelector(`${c}[${o[h]}='${s[o[h]]}']`)),!p);h++);p&&l||(p||(p=document.createElement(c)),o.forEach(h=>p.setAttribute(h,s[h])),document.querySelector("head").appendChild(p))}function Q({routePattern:c,currentRoute:l}){let s={},o=c.split("/"),p=l.split("/");return o.forEach((h,b)=>{h.startsWith(":")&&(s[h.slice(1)]=p[b])}),s}class E extends $.Component{constructor(l){super(l),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(l,s){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",l),console.error("[Routerino] Component Stack:",s.componentStack),this.props.routePath&&console.error("[Routerino] Failed Route:",this.props.routePath),console.error("[Routerino] Error occurred at:",new Date().toISOString()),console.groupEnd()),document.title=this.props.errorTitleString,this.props.usePrerenderTags&&i({name:"prerender-status-code",content:"500"})}render(){return this.state.hasError?this.props.fallback:this.props.children}}E.propTypes={children:t.node,fallback:t.node,errorTitleString:t.string.isRequired,usePrerenderTags:t.bool,routePath:t.string,debug:t.bool};function S({routes:c=[{path:"/",element:a.jsx("p",{children:"This is the default route. Pass an array of routes to the Routerino component in order to configure your own pages. Each route is a dictionary with at least `path` and `element` defined."}),title:"Routerino default route example",description:"The default route example description.",tags:[{property:"og:locale",content:"en_US"}]}],notFoundTemplate:l=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"No page found for this URL. [404]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:s="Page not found [404]",errorTemplate:o=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"Page failed to load. [500]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:p="Page error [500]",useTrailingSlash:h=!0,usePrerenderTags:b=!1,baseUrl:X=null,title:v="",separator:y=" | ",imageUrl:C=null,touchIconUrl:B=null,debug:u=!1}){var F,H,j,A,I,M,O;const L=`${p}${y}${v}`,W=`${s}${y}${v}`;try{if(u){const e=c.map(m=>m.path),d=e.filter((m,f)=>e.indexOf(m)!==f);d.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(d)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[k,D]=$.useState(((F=window==null?void 0:window.location)==null?void 0:F.href)??"/");$.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const e=m=>{u&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let f=m.target;for(;f.tagName!=="A"&&f.parentElement;)f=f.parentElement;if(f.tagName!=="A"){u&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const w=f.getAttribute("href")||f.href;if(!w){u&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(w)){u&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",w);return}u&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",w);let R;try{R=new URL(w,window.location.href)}catch(U){u&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",w,U);return}u&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",R,"current:",window.location),R&&window.location.origin===R.origin?(u&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),m.preventDefault(),f.href!==window.location.href&&(D(f.href),window.history.pushState({},"",f.href)),window.scrollTo({top:0,behavior:"auto"})):u&&console.debug("%c[Routerino]%c target link does not share an origin, standard browser link handling applies","color: #6b7280; font-weight: bold","")};document.addEventListener("click",e);const d=()=>{u&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),D(window.location.href)};return window.addEventListener("popstate",d),()=>{document.removeEventListener("click",e),window.removeEventListener("popstate",d)}},[k]);let r=((H=window==null?void 0:window.location)==null?void 0:H.pathname)??"/";(r==="/index.html"||r==="")&&(r="/");const _=c.find(e=>e.path===r),z=c.find(e=>`${e.path}/`===r||e.path===`${r}/`),T=c.find(e=>{const d=e.path.endsWith("/")?e.path.slice(0,-1):e.path,m=r.endsWith("/")?r.slice(0,-1):r,f=d.split("/").filter(Boolean),w=m.split("/").filter(Boolean);return f.length!==w.length?!1:f.every((R,U)=>R.startsWith(":")?!0:R===w[U])}),n=_??z??T;if(u&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:n,exactMatch:_,addSlashMatch:z,paramsMatch:T}),!n)return u&&(console.group("%c[Routerino]%c 404 - No matching route","color: #f59e0b; font-weight: bold",""),console.warn("%c[Routerino]%c Requested path:","color: #f59e0b; font-weight: bold","",r),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",c.map(e=>e.path)),console.groupEnd()),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l;if(b){const e=document.querySelector('meta[name="prerender-status-code"]');e&&e.remove();const d=document.querySelector('meta[name="prerender-header"]');d&&d.remove()}const G=h&&!r.endsWith("/")&&r!=="/",K=!h&&r.endsWith("/")&&r!=="/",Y=G?`${r}/`:K?r.slice(0,-1):r,x=`${X??((j=window==null?void 0:window.location)==null?void 0:j.origin)??""}${Y}`;if(n.title){const e=`${n.title}${y}${v}`;document.title=e,i({tag:"link",rel:"canonical",href:x}),(A=n.tags)!=null&&A.find(({property:d})=>d==="og:title")||i({property:"og:title",content:e}),(I=n.tags)!=null&&I.find(({property:d})=>d==="og:url")||i({property:"og:url",content:x})}if(n.description&&(i({name:"description",content:n.description}),(M=n.tags)!=null&&M.find(({property:e})=>e==="og:description")||i({property:"og:description",content:n.description})),(C||n.imageUrl)&&i({property:"og:image",content:n.imageUrl??C}),(O=n.tags)!=null&&O.find(({property:e})=>e==="twitter:card")||i({name:"twitter:card",content:"summary_large_image"}),B&&i({tag:"link",rel:"apple-touch-icon",href:B}),b&&(G||K)&&(i({name:"prerender-status-code",content:"301"}),i({name:"prerender-header",content:`Location: ${x}`})),n.tags&&n.tags.length?(n.tags.find(({property:e})=>e==="og:type")||i({property:"og:type",content:"website"}),n.tags.forEach(e=>i(e))):i({property:"og:type",content:"website"}),n.element){const e=Q({routePattern:n.path,currentRoute:r}),d={currentRoute:r,params:e,routePattern:n.path,updateHeadTag:i};return a.jsx(q.Provider,{value:d,children:a.jsx(E,{fallback:o,errorTitleString:L,usePrerenderTags:b,routePath:r,debug:u,children:n.element})})}return u&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",r),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l}catch(k){return u&&(console.group("%c[Routerino]%c Fatal Error","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c An error occurred in the router itself (not in a route component)","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c Error:","color: #ff6b6b; font-weight: bold","",k),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),b&&i({name:"prerender-status-code",content:"500"}),document.title=L,o}}const V=t.exact({path:(c,l,s)=>{const o=c[l];return o==null?new Error(`The prop \`${l}\` is marked as required in \`${s}\`, but its value is \`${o}\`.`):typeof o!="string"?new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`):o.startsWith("/")?null:new Error(`Invalid prop \`${l}\` value \`${o}\` supplied to \`${s}\`. Route paths must start with a forward slash (/).`)},element:t.element.isRequired,title:t.string,description:t.string,tags:t.arrayOf(t.object),imageUrl:t.string});S.propTypes={routes:t.arrayOf(V),title:t.string,separator:t.string,notFoundTemplate:t.element,notFoundTitle:t.string,errorTemplate:t.element,errorTitle:t.string,useTrailingSlash:t.bool,usePrerenderTags:t.bool,baseUrl:(c,l,s)=>{const o=c[l];if(o!=null){if(typeof o!="string")return new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`);if(o.endsWith("/"))return new Error(`Invalid prop \`${l}\` supplied to \`${s}\`. The baseUrl should not end with a slash. Got: "${o}"`)}return null},imageUrl:t.string,touchIconUrl:t.string,debug:t.bool},g.ErrorBoundary=E,g.Routerino=S,g.default=S,g.updateHeadTag=i,g.useRouterino=J,Object.defineProperties(g,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routerino",
3
- "version": "2.2.2-rc2",
3
+ "version": "2.2.2-rc4",
4
4
  "description": "A lightweight, SEO-optimized React router for modern web applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -474,7 +474,9 @@ const routes = routesModule.routes || routesModule.default;
474
474
  const notFoundTemplate = routesModule.notFoundTemplate;
475
475
 
476
476
  // Check if App component is exported from routes file
477
- const App = routesModule.App || routesModule.default?.App;
477
+ // App can be: named export, default export, or App property on default export
478
+ const App = routesModule.App ||
479
+ (typeof routesModule.default === 'function' ? routesModule.default : routesModule.default?.App);
478
480
 
479
481
  if (!routes) {
480
482
  throw new Error('Could not find routes export. Expected "export const routes" or "export default" from ${relativePath}');
@@ -508,37 +510,65 @@ function mockWindow(url, baseUrl) {
508
510
  removeEventListener: () => {},
509
511
  dispatchEvent: () => {}
510
512
  };
513
+ // Mock for document with more complete implementation
514
+ const mockElements = [];
511
515
  global.document = {
516
+ title: '', // Mock title property for SSG
512
517
  addEventListener: () => {},
513
518
  removeEventListener: () => {},
514
- querySelector: () => null,
515
- createElement: () => ({
516
- setAttribute: () => {},
517
- appendChild: () => {}
518
- }),
519
+ querySelector: (selector) => {
520
+ // Return mock head for head selector
521
+ if (selector === 'head') {
522
+ return {
523
+ appendChild: (elem) => {
524
+ mockElements.push(elem);
525
+ return elem;
526
+ }
527
+ };
528
+ }
529
+ // For meta tag queries, return null (tag not found)
530
+ return null;
531
+ },
532
+ createElement: (tagName) => {
533
+ const elem = {
534
+ tagName,
535
+ attributes: {},
536
+ setAttribute: function(name, value) {
537
+ this.attributes[name] = value;
538
+ },
539
+ appendChild: () => {}
540
+ };
541
+ return elem;
542
+ },
519
543
  head: {
520
- appendChild: () => {}
544
+ appendChild: (elem) => {
545
+ mockElements.push(elem);
546
+ return elem;
547
+ },
548
+ querySelector: () => null,
549
+ querySelectorAll: () => []
521
550
  }
522
551
  };
523
552
  }
524
553
 
525
554
  export function render(url, baseUrl) {
526
- // Check if we should render the full App or just the route element
555
+ // Check if we should render the full App or just the route element
527
556
  if (App) {
557
+ // Find the route to render
558
+ const route = routes.find(r => {
559
+ if (r.path === url) return true;
560
+ if (r.path === '/' && url === '/') return true;
561
+ if (isDynamicRoute(r.path)) return false;
562
+ return r.path === url;
563
+ });
564
+
528
565
  // Mock window for the current route
529
566
  mockWindow(url, baseUrl);
530
567
 
531
568
  try {
532
- // Render the full App component (which includes Routerino)
533
- const html = ReactDOMServer.renderToString(React.createElement(App));
534
569
 
535
- // Find the rendered route to get its metadata
536
- const route = routes.find(r => {
537
- if (r.path === url) return true;
538
- if (r.path === '/' && url === '/') return true;
539
- if (isDynamicRoute(r.path)) return false;
540
- return r.path === url;
541
- });
570
+ // Render the App with Routerino SSG-aware
571
+ const html = ReactDOMServer.renderToString(React.createElement(App));
542
572
 
543
573
  return {
544
574
  html,
@@ -549,6 +579,7 @@ export function render(url, baseUrl) {
549
579
  };
550
580
  } catch (error) {
551
581
  console.error(\`[Routerino Forge] Failed to render App for route \${url}:\`, error.message);
582
+ console.error(\`[Routerino Forge] Stack trace:\`, error.stack);
552
583
  // Fall back to route-only rendering
553
584
  } finally {
554
585
  // Clean up global mocks
@@ -558,12 +589,17 @@ export function render(url, baseUrl) {
558
589
  }
559
590
 
560
591
  // Original behavior: render just the route element
561
- const route = routes.find(r => {
592
+ // Need to find the route again if App path wasn't taken
593
+ const route = App ? null : routes.find(r => {
562
594
  if (r.path === url) return true;
563
595
  if (r.path === '/' && url === '/') return true;
564
596
  if (isDynamicRoute(r.path)) return false;
565
597
  return r.path === url;
566
- });
598
+ });
599
+
600
+ // If we get here and App was defined, it means the App render failed
601
+ // Return early to avoid duplicate rendering
602
+ if (App) return;
567
603
 
568
604
  if (!route) {
569
605
  if (notFoundTemplate) {