routerino 2.2.2 → 2.2.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.
- package/dist/routerino.js +37 -40
- package/dist/routerino.umd.cjs +1 -1
- package/package.json +13 -13
- package/routerino-forge.js +22 -59
package/dist/routerino.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { jsx as g, jsxs as
|
|
2
|
-
import { createContext as
|
|
1
|
+
import { jsx as g, jsxs as W, Fragment as A } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as O, Component as j, useContext as M, useState as _, useEffect as z } from "react";
|
|
3
3
|
import t from "prop-types";
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const i =
|
|
4
|
+
const F = O(null);
|
|
5
|
+
function Y() {
|
|
6
|
+
const i = M(F);
|
|
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
|
|
26
|
+
function G({ 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
|
|
32
|
+
class H extends j {
|
|
33
33
|
constructor(c) {
|
|
34
34
|
super(c), this.state = { hasError: !1 };
|
|
35
35
|
}
|
|
@@ -48,7 +48,7 @@ class z extends Q {
|
|
|
48
48
|
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
H.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 @@ z.propTypes = {
|
|
|
62
62
|
/** Whether to log debug messages to console (optional) */
|
|
63
63
|
debug: t.bool
|
|
64
64
|
};
|
|
65
|
-
function
|
|
65
|
+
function K({
|
|
66
66
|
routes: i = [
|
|
67
67
|
{
|
|
68
68
|
path: "/",
|
|
@@ -72,26 +72,25 @@ function N({
|
|
|
72
72
|
tags: [{ property: "og:locale", content: "en_US" }]
|
|
73
73
|
}
|
|
74
74
|
],
|
|
75
|
-
notFoundTemplate: c = /* @__PURE__ */
|
|
75
|
+
notFoundTemplate: c = /* @__PURE__ */ W(A, { 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__ */
|
|
80
|
+
errorTemplate: o = /* @__PURE__ */ W(A, { 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:
|
|
87
|
+
baseUrl: I = 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, B, W;
|
|
95
94
|
const k = `${h}${y}${R}`, x = `${a}${y}${R}`;
|
|
96
95
|
try {
|
|
97
96
|
if (s) {
|
|
@@ -109,10 +108,8 @@ function N({
|
|
|
109
108
|
""
|
|
110
109
|
));
|
|
111
110
|
}
|
|
112
|
-
const [$,
|
|
113
|
-
|
|
114
|
-
if (typeof window > "u" || typeof document > "u")
|
|
115
|
-
return;
|
|
111
|
+
const [$, P] = _(window.location.href);
|
|
112
|
+
z(() => {
|
|
116
113
|
const e = (w) => {
|
|
117
114
|
s && console.debug(
|
|
118
115
|
"%c[Routerino]%c click occurred",
|
|
@@ -178,7 +175,7 @@ function N({
|
|
|
178
175
|
"%c[Routerino]%c target link is same origin, will use push-state transitioning",
|
|
179
176
|
"color: #6b7280; font-weight: bold",
|
|
180
177
|
""
|
|
181
|
-
), w.preventDefault(), d.href !== window.location.href && (
|
|
178
|
+
), w.preventDefault(), d.href !== window.location.href && (P(d.href), window.history.pushState({}, "", d.href)), window.scrollTo({
|
|
182
179
|
top: 0,
|
|
183
180
|
behavior: "auto"
|
|
184
181
|
})) : s && console.debug(
|
|
@@ -194,25 +191,25 @@ function N({
|
|
|
194
191
|
"color: #6b7280; font-weight: bold",
|
|
195
192
|
"",
|
|
196
193
|
window.location.pathname
|
|
197
|
-
),
|
|
194
|
+
), P(window.location.href);
|
|
198
195
|
};
|
|
199
196
|
return window.addEventListener("popstate", u), () => {
|
|
200
197
|
document.removeEventListener("click", e), window.removeEventListener("popstate", u);
|
|
201
198
|
};
|
|
202
199
|
}, [$]);
|
|
203
|
-
let r =
|
|
200
|
+
let r = window.location?.pathname ?? "/";
|
|
204
201
|
(r === "/index.html" || r === "") && (r = "/");
|
|
205
|
-
const
|
|
202
|
+
const U = i.find((e) => e.path === r), C = i.find(
|
|
206
203
|
(e) => `${e.path}/` === r || e.path === `${r}/`
|
|
207
|
-
),
|
|
204
|
+
), L = i.find((e) => {
|
|
208
205
|
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);
|
|
209
206
|
return d.length !== p.length ? !1 : d.every((b, S) => b.startsWith(":") ? !0 : b === p[S]);
|
|
210
|
-
}), n =
|
|
207
|
+
}), n = U ?? C ?? L;
|
|
211
208
|
if (s && console.debug(
|
|
212
209
|
"%c[Routerino]%c Route matching:",
|
|
213
210
|
"color: #6b7280; font-weight: bold",
|
|
214
211
|
"",
|
|
215
|
-
{ match: n, exactMatch:
|
|
212
|
+
{ match: n, exactMatch: U, addSlashMatch: C, paramsMatch: L }
|
|
216
213
|
), !n)
|
|
217
214
|
return s && (console.group(
|
|
218
215
|
"%c[Routerino]%c 404 - No matching route",
|
|
@@ -239,39 +236,39 @@ function N({
|
|
|
239
236
|
);
|
|
240
237
|
u && u.remove();
|
|
241
238
|
}
|
|
242
|
-
const
|
|
239
|
+
const q = f && !r.endsWith("/") && r !== "/", B = !f && r.endsWith("/") && r !== "/", D = q ? `${r}/` : B ? r.slice(0, -1) : r, E = `${I || window.location.origin}${D}`;
|
|
243
240
|
if (n.title) {
|
|
244
241
|
const e = `${n.title}${y}${R}`;
|
|
245
242
|
document.title = e, l({
|
|
246
243
|
tag: "link",
|
|
247
244
|
rel: "canonical",
|
|
248
245
|
href: E
|
|
249
|
-
}),
|
|
246
|
+
}), n.tags?.find(({ property: u }) => u === "og:title") || l({
|
|
250
247
|
property: "og:title",
|
|
251
248
|
content: e
|
|
252
|
-
}),
|
|
249
|
+
}), n.tags?.find(({ property: u }) => u === "og:url") || l({
|
|
253
250
|
property: "og:url",
|
|
254
251
|
content: E
|
|
255
252
|
});
|
|
256
253
|
}
|
|
257
|
-
if (n.description && (l({ name: "description", content: n.description }),
|
|
254
|
+
if (n.description && (l({ name: "description", content: n.description }), n.tags?.find(({ property: e }) => e === "og:description") || l({
|
|
258
255
|
property: "og:description",
|
|
259
256
|
content: n.description
|
|
260
257
|
})), (v || n.imageUrl) && l({
|
|
261
258
|
property: "og:image",
|
|
262
259
|
content: n.imageUrl ?? v
|
|
263
|
-
}),
|
|
260
|
+
}), n.tags?.find(({ property: e }) => e === "twitter:card") || l({
|
|
264
261
|
name: "twitter:card",
|
|
265
262
|
content: "summary_large_image"
|
|
266
263
|
}), T && l({
|
|
267
264
|
tag: "link",
|
|
268
265
|
rel: "apple-touch-icon",
|
|
269
266
|
href: T
|
|
270
|
-
}), m && (
|
|
267
|
+
}), m && (q || B) && (l({ name: "prerender-status-code", content: "301" }), l({
|
|
271
268
|
name: "prerender-header",
|
|
272
269
|
content: `Location: ${E}`
|
|
273
270
|
})), 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) {
|
|
274
|
-
const e =
|
|
271
|
+
const e = G({
|
|
275
272
|
routePattern: n.path,
|
|
276
273
|
currentRoute: r
|
|
277
274
|
}), u = {
|
|
@@ -280,8 +277,8 @@ function N({
|
|
|
280
277
|
routePattern: n.path,
|
|
281
278
|
updateHeadTag: l
|
|
282
279
|
};
|
|
283
|
-
return /* @__PURE__ */ g(
|
|
284
|
-
|
|
280
|
+
return /* @__PURE__ */ g(F.Provider, { value: u, children: /* @__PURE__ */ g(
|
|
281
|
+
H,
|
|
285
282
|
{
|
|
286
283
|
fallback: o,
|
|
287
284
|
errorTitleString: k,
|
|
@@ -319,7 +316,7 @@ function N({
|
|
|
319
316
|
), console.groupEnd()), m && l({ name: "prerender-status-code", content: "500" }), document.title = k, o;
|
|
320
317
|
}
|
|
321
318
|
}
|
|
322
|
-
const
|
|
319
|
+
const J = t.exact({
|
|
323
320
|
path: (i, c, a) => {
|
|
324
321
|
const o = i[c];
|
|
325
322
|
return o == null ? new Error(
|
|
@@ -336,8 +333,8 @@ const ee = t.exact({
|
|
|
336
333
|
tags: t.arrayOf(t.object),
|
|
337
334
|
imageUrl: t.string
|
|
338
335
|
});
|
|
339
|
-
|
|
340
|
-
routes: t.arrayOf(
|
|
336
|
+
K.propTypes = {
|
|
337
|
+
routes: t.arrayOf(J),
|
|
341
338
|
title: t.string,
|
|
342
339
|
separator: t.string,
|
|
343
340
|
notFoundTemplate: t.element,
|
|
@@ -365,9 +362,9 @@ N.propTypes = {
|
|
|
365
362
|
debug: t.bool
|
|
366
363
|
};
|
|
367
364
|
export {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
H as ErrorBoundary,
|
|
366
|
+
K as Routerino,
|
|
367
|
+
K as default,
|
|
371
368
|
l as updateHeadTag,
|
|
372
|
-
|
|
369
|
+
Y as useRouterino
|
|
373
370
|
};
|
package/dist/routerino.umd.cjs
CHANGED
|
@@ -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
|
|
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 O(){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 D({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:z=null,title:v="",separator:y=" | ",imageUrl:C=null,touchIconUrl:B=null,debug:u=!1}){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,F]=$.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&&(F(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),F(window.location.href)};return window.addEventListener("popstate",d),()=>{document.removeEventListener("click",e),window.removeEventListener("popstate",d)}},[k]);let r=window.location?.pathname??"/";(r==="/index.html"||r==="")&&(r="/");const H=c.find(e=>e.path===r),j=c.find(e=>`${e.path}/`===r||e.path===`${r}/`),A=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=H??j??A;if(u&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:n,exactMatch:H,addSlashMatch:j,paramsMatch:A}),!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 I=h&&!r.endsWith("/")&&r!=="/",M=!h&&r.endsWith("/")&&r!=="/",T=I?`${r}/`:M?r.slice(0,-1):r,x=`${z||window.location.origin}${T}`;if(n.title){const e=`${n.title}${y}${v}`;document.title=e,i({tag:"link",rel:"canonical",href:x}),n.tags?.find(({property:d})=>d==="og:title")||i({property:"og:title",content:e}),n.tags?.find(({property:d})=>d==="og:url")||i({property:"og:url",content:x})}if(n.description&&(i({name:"description",content:n.description}),n.tags?.find(({property:e})=>e==="og:description")||i({property:"og:description",content:n.description})),(C||n.imageUrl)&&i({property:"og:image",content:n.imageUrl??C}),n.tags?.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&&(I||M)&&(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=D({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 _=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(_),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=O,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.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "A lightweight, SEO-optimized React router for modern web applications",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -55,24 +55,24 @@
|
|
|
55
55
|
"prepare": "husky"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@eslint/js": "^9.
|
|
58
|
+
"@eslint/js": "^9.38.0",
|
|
59
59
|
"@testing-library/react": "^16.3.0",
|
|
60
60
|
"@testing-library/user-event": "^14.6.1",
|
|
61
|
-
"@vitejs/plugin-react": "^
|
|
62
|
-
"eslint": "^9.
|
|
61
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
62
|
+
"eslint": "^9.38.0",
|
|
63
63
|
"eslint-plugin-react": "^7.37.5",
|
|
64
|
-
"express": "^
|
|
65
|
-
"globals": "^16.
|
|
64
|
+
"express": "^5.1.0",
|
|
65
|
+
"globals": "^16.4.0",
|
|
66
66
|
"husky": "^9.1.7",
|
|
67
67
|
"jsdom": "^26.1.0",
|
|
68
|
-
"lint-staged": "^16.
|
|
68
|
+
"lint-staged": "^16.2.6",
|
|
69
69
|
"node-fetch": "^3.3.2",
|
|
70
70
|
"prettier": "^3.6.2",
|
|
71
71
|
"prop-types": "^15.8.1",
|
|
72
|
-
"react": "^19.
|
|
73
|
-
"react-dom": "^19.
|
|
74
|
-
"vite": "^
|
|
75
|
-
"vitest": "^
|
|
72
|
+
"react": "^19.2.0",
|
|
73
|
+
"react-dom": "^19.2.0",
|
|
74
|
+
"vite": "^7.1.12",
|
|
75
|
+
"vitest": "^4.0.2"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"prop-types": "^15.0.0",
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
"node": ">=18"
|
|
92
92
|
},
|
|
93
93
|
"volta": {
|
|
94
|
-
"node": "22.
|
|
95
|
-
"npm": "10.9.
|
|
94
|
+
"node": "22.21.0",
|
|
95
|
+
"npm": "10.9.4"
|
|
96
96
|
},
|
|
97
97
|
"lint-staged": {
|
|
98
98
|
"*.{js,jsx,mjs,cjs}": [
|
package/routerino-forge.js
CHANGED
|
@@ -474,9 +474,7 @@ 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
|
-
|
|
478
|
-
const App = routesModule.App ||
|
|
479
|
-
(typeof routesModule.default === 'function' ? routesModule.default : routesModule.default?.App);
|
|
477
|
+
const App = routesModule.App || routesModule.default?.App;
|
|
480
478
|
|
|
481
479
|
if (!routes) {
|
|
482
480
|
throw new Error('Could not find routes export. Expected "export const routes" or "export default" from ${relativePath}');
|
|
@@ -484,7 +482,7 @@ if (!routes) {
|
|
|
484
482
|
|
|
485
483
|
// Helper to check if a route is dynamic (contains :param)
|
|
486
484
|
const isDynamicRoute = (path) => path.split("/").some(segment => segment.startsWith(":"));
|
|
487
|
-
export { routes
|
|
485
|
+
export { routes };
|
|
488
486
|
|
|
489
487
|
// Mock minimal window object for SSG
|
|
490
488
|
function mockWindow(url, baseUrl) {
|
|
@@ -510,66 +508,38 @@ function mockWindow(url, baseUrl) {
|
|
|
510
508
|
removeEventListener: () => {},
|
|
511
509
|
dispatchEvent: () => {}
|
|
512
510
|
};
|
|
513
|
-
// Mock for document with more complete implementation
|
|
514
|
-
const mockElements = [];
|
|
515
511
|
global.document = {
|
|
516
|
-
title: '', // Mock title property for SSG
|
|
517
512
|
addEventListener: () => {},
|
|
518
513
|
removeEventListener: () => {},
|
|
519
|
-
querySelector: (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
},
|
|
514
|
+
querySelector: () => null,
|
|
515
|
+
createElement: () => ({
|
|
516
|
+
setAttribute: () => {},
|
|
517
|
+
appendChild: () => {}
|
|
518
|
+
}),
|
|
543
519
|
head: {
|
|
544
|
-
appendChild: (
|
|
545
|
-
mockElements.push(elem);
|
|
546
|
-
return elem;
|
|
547
|
-
},
|
|
548
|
-
querySelector: () => null,
|
|
549
|
-
querySelectorAll: () => []
|
|
520
|
+
appendChild: () => {}
|
|
550
521
|
}
|
|
551
522
|
};
|
|
552
523
|
}
|
|
553
524
|
|
|
554
525
|
export function render(url, baseUrl) {
|
|
555
|
-
// Check if we should render the full App or just the route element
|
|
526
|
+
// Check if we should render the full App or just the route element
|
|
556
527
|
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
|
-
|
|
565
528
|
// Mock window for the current route
|
|
566
529
|
mockWindow(url, baseUrl);
|
|
567
530
|
|
|
568
531
|
try {
|
|
569
|
-
|
|
570
|
-
// Render the App with Routerino SSG-aware
|
|
532
|
+
// Render the full App component (which includes Routerino)
|
|
571
533
|
const html = ReactDOMServer.renderToString(React.createElement(App));
|
|
572
534
|
|
|
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
|
+
});
|
|
542
|
+
|
|
573
543
|
return {
|
|
574
544
|
html,
|
|
575
545
|
title: route?.title,
|
|
@@ -579,7 +549,6 @@ export function render(url, baseUrl) {
|
|
|
579
549
|
};
|
|
580
550
|
} catch (error) {
|
|
581
551
|
console.error(\`[Routerino Forge] Failed to render App for route \${url}:\`, error.message);
|
|
582
|
-
console.error(\`[Routerino Forge] Stack trace:\`, error.stack);
|
|
583
552
|
// Fall back to route-only rendering
|
|
584
553
|
} finally {
|
|
585
554
|
// Clean up global mocks
|
|
@@ -589,17 +558,12 @@ export function render(url, baseUrl) {
|
|
|
589
558
|
}
|
|
590
559
|
|
|
591
560
|
// Original behavior: render just the route element
|
|
592
|
-
|
|
593
|
-
const route = App ? null : routes.find(r => {
|
|
561
|
+
const route = routes.find(r => {
|
|
594
562
|
if (r.path === url) return true;
|
|
595
563
|
if (r.path === '/' && url === '/') return true;
|
|
596
564
|
if (isDynamicRoute(r.path)) return false;
|
|
597
565
|
return r.path === url;
|
|
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;
|
|
566
|
+
});
|
|
603
567
|
|
|
604
568
|
if (!route) {
|
|
605
569
|
if (notFoundTemplate) {
|
|
@@ -777,7 +741,7 @@ export function render(url, baseUrl) {
|
|
|
777
741
|
const reductionPercent = (
|
|
778
742
|
(1 - imageStats.placeholderSize / imageStats.totalSize) *
|
|
779
743
|
100
|
|
780
|
-
).toFixed(
|
|
744
|
+
).toFixed(0);
|
|
781
745
|
console.log(
|
|
782
746
|
`[Routerino Forge] ✓ Optimized ${imageStats.processed} images (${totalSizeMB}MB total, ${placeholderSizeKB}KB placeholders, ${reductionPercent}% reduction)`
|
|
783
747
|
);
|
|
@@ -1104,7 +1068,7 @@ async function generate404Page({ template, outputDir, config, render }) {
|
|
|
1104
1068
|
config.baseUrl
|
|
1105
1069
|
);
|
|
1106
1070
|
|
|
1107
|
-
// The render function will return the notFoundTemplate HTML
|
|
1071
|
+
// The render function will return the notFoundTemplate HTML
|
|
1108
1072
|
const renderedHTML = renderResult.html || "404 - Page Not Found";
|
|
1109
1073
|
|
|
1110
1074
|
// Generate meta tags for 404 page
|
|
@@ -1230,5 +1194,4 @@ Sitemap: ${config.baseUrl}/sitemap.xml`;
|
|
|
1230
1194
|
}
|
|
1231
1195
|
}
|
|
1232
1196
|
|
|
1233
|
-
// Default export
|
|
1234
1197
|
export default routerinoForge;
|