routerino 2.2.2 → 2.2.4
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/README.md +92 -1
- package/dist/routerino.js +37 -38
- package/dist/routerino.umd.cjs +1 -1
- package/package.json +13 -13
- package/routerino-forge.js +0 -1
package/README.md
CHANGED
|
@@ -708,7 +708,7 @@ export default defineConfig({
|
|
|
708
708
|
- `/about/` → `about/index.html`
|
|
709
709
|
- Canonical URLs and redirects based on `useTrailingSlash` setting
|
|
710
710
|
- Prerender compatible 301 redirects for non-canonical versions
|
|
711
|
-
- **Static host ready**: Output format aligns perfectly with
|
|
711
|
+
- **Static host ready**: Output format aligns perfectly with any static host such as Netlify and Cloudflare Pages
|
|
712
712
|
- Routes generate `/path/index.html` for clean URLs (and `/path.html` for compatibility with no-slash URLs)
|
|
713
713
|
- `404.html` at root for custom error pages
|
|
714
714
|
- No server configuration needed - just deploy!
|
|
@@ -1216,6 +1216,97 @@ import Routerino from "./vendor/routerino";
|
|
|
1216
1216
|
|
|
1217
1217
|
By vendoring Routerino, you have full control over the code and can make any necessary modifications directly to the `routerino.jsx` file. However, keep in mind that you'll need to manually update the vendored file if you want to incorporate any future updates or bug fixes from the main Routerino repository.
|
|
1218
1218
|
|
|
1219
|
+
## Accessibility & SEO Best Practices
|
|
1220
|
+
|
|
1221
|
+
To maximize your PageSpeed Insights and Lighthouse scores, we recommend setting up ESLint with accessibility rules. This helps catch common issues that hurt SEO and user experience.
|
|
1222
|
+
|
|
1223
|
+
### Setting up eslint-plugin-jsx-a11y
|
|
1224
|
+
|
|
1225
|
+
1. Install the plugin:
|
|
1226
|
+
|
|
1227
|
+
```bash
|
|
1228
|
+
npm install --save-dev eslint-plugin-jsx-a11y
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
2. Add to your ESLint config:
|
|
1232
|
+
|
|
1233
|
+
```javascript
|
|
1234
|
+
// eslint.config.js (ESLint 9+ flat config)
|
|
1235
|
+
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
1236
|
+
|
|
1237
|
+
export default [
|
|
1238
|
+
{
|
|
1239
|
+
plugins: {
|
|
1240
|
+
"jsx-a11y": jsxA11y,
|
|
1241
|
+
},
|
|
1242
|
+
rules: {
|
|
1243
|
+
...jsxA11y.configs.recommended.rules,
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
];
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
3. Add a lint run script
|
|
1250
|
+
|
|
1251
|
+
```json
|
|
1252
|
+
{
|
|
1253
|
+
"scripts": {
|
|
1254
|
+
"lint:": "eslint --ext .jsx,.js src/"
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
See https://github.com/jsx-eslint/eslint-plugin-jsx-a11y for more info.
|
|
1260
|
+
|
|
1261
|
+
### Key Rules for Accessibility
|
|
1262
|
+
|
|
1263
|
+
1. **Images**: Include descriptive `alt` text
|
|
1264
|
+
|
|
1265
|
+
```jsx
|
|
1266
|
+
// ❌ Bad - Missing alt text
|
|
1267
|
+
<img src="/logo.png" />
|
|
1268
|
+
|
|
1269
|
+
// ✅ Good - Descriptive alt text
|
|
1270
|
+
<img src="/logo.png" alt="Company logo" />
|
|
1271
|
+
|
|
1272
|
+
// ✅ Good - Decorative images
|
|
1273
|
+
<img src="/decoration.png" alt="" role="presentation" />
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
2. **Heading Hierarchy**: Use proper heading order
|
|
1277
|
+
|
|
1278
|
+
```jsx
|
|
1279
|
+
// ❌ Bad - Skipping heading levels
|
|
1280
|
+
<h1>Page Title</h1>
|
|
1281
|
+
<h3>Subsection</h3> // Should be h2
|
|
1282
|
+
|
|
1283
|
+
// ✅ Good - Proper hierarchy
|
|
1284
|
+
<h1>Page Title</h1>
|
|
1285
|
+
<h2>Main Section</h2>
|
|
1286
|
+
<h3>Subsection</h3>
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
3. **Link Text**: Avoid generic link text
|
|
1290
|
+
|
|
1291
|
+
```jsx
|
|
1292
|
+
// ❌ Bad - Generic text
|
|
1293
|
+
<a href="/products">Click here</a>
|
|
1294
|
+
|
|
1295
|
+
// ✅ Good - Descriptive text
|
|
1296
|
+
<a href="/products">View our products</a>
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
4. **ARIA Labels**: Use for icon-only buttons
|
|
1300
|
+
|
|
1301
|
+
```jsx
|
|
1302
|
+
// ✅ Good - Icon button with label
|
|
1303
|
+
<button aria-label="Close dialog">
|
|
1304
|
+
<svg>...</svg>
|
|
1305
|
+
</button>
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
These practices will help you achieve better accessibility scores and ensure your site is accessible to all users.
|
|
1309
|
+
|
|
1219
1310
|
## Additional Resources
|
|
1220
1311
|
|
|
1221
1312
|
Here are some sources for further reading on SEO best-practices.
|
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,8 +108,8 @@ function N({
|
|
|
109
108
|
""
|
|
110
109
|
));
|
|
111
110
|
}
|
|
112
|
-
const [$,
|
|
113
|
-
|
|
111
|
+
const [$, P] = _(window?.location?.href ?? "/");
|
|
112
|
+
z(() => {
|
|
114
113
|
if (typeof window > "u" || typeof document > "u")
|
|
115
114
|
return;
|
|
116
115
|
const e = (w) => {
|
|
@@ -178,7 +177,7 @@ function N({
|
|
|
178
177
|
"%c[Routerino]%c target link is same origin, will use push-state transitioning",
|
|
179
178
|
"color: #6b7280; font-weight: bold",
|
|
180
179
|
""
|
|
181
|
-
), w.preventDefault(), d.href !== window.location.href && (
|
|
180
|
+
), w.preventDefault(), d.href !== window.location.href && (P(d.href), window.history.pushState({}, "", d.href)), window.scrollTo({
|
|
182
181
|
top: 0,
|
|
183
182
|
behavior: "auto"
|
|
184
183
|
})) : s && console.debug(
|
|
@@ -194,25 +193,25 @@ function N({
|
|
|
194
193
|
"color: #6b7280; font-weight: bold",
|
|
195
194
|
"",
|
|
196
195
|
window.location.pathname
|
|
197
|
-
),
|
|
196
|
+
), P(window.location.href);
|
|
198
197
|
};
|
|
199
198
|
return window.addEventListener("popstate", u), () => {
|
|
200
199
|
document.removeEventListener("click", e), window.removeEventListener("popstate", u);
|
|
201
200
|
};
|
|
202
201
|
}, [$]);
|
|
203
|
-
let r =
|
|
202
|
+
let r = window?.location?.pathname ?? "/";
|
|
204
203
|
(r === "/index.html" || r === "") && (r = "/");
|
|
205
|
-
const
|
|
204
|
+
const U = i.find((e) => e.path === r), C = i.find(
|
|
206
205
|
(e) => `${e.path}/` === r || e.path === `${r}/`
|
|
207
|
-
),
|
|
206
|
+
), L = i.find((e) => {
|
|
208
207
|
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
208
|
return d.length !== p.length ? !1 : d.every((b, S) => b.startsWith(":") ? !0 : b === p[S]);
|
|
210
|
-
}), n =
|
|
209
|
+
}), n = U ?? C ?? L;
|
|
211
210
|
if (s && console.debug(
|
|
212
211
|
"%c[Routerino]%c Route matching:",
|
|
213
212
|
"color: #6b7280; font-weight: bold",
|
|
214
213
|
"",
|
|
215
|
-
{ match: n, exactMatch:
|
|
214
|
+
{ match: n, exactMatch: U, addSlashMatch: C, paramsMatch: L }
|
|
216
215
|
), !n)
|
|
217
216
|
return s && (console.group(
|
|
218
217
|
"%c[Routerino]%c 404 - No matching route",
|
|
@@ -239,39 +238,39 @@ function N({
|
|
|
239
238
|
);
|
|
240
239
|
u && u.remove();
|
|
241
240
|
}
|
|
242
|
-
const
|
|
241
|
+
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
242
|
if (n.title) {
|
|
244
243
|
const e = `${n.title}${y}${R}`;
|
|
245
244
|
document.title = e, l({
|
|
246
245
|
tag: "link",
|
|
247
246
|
rel: "canonical",
|
|
248
247
|
href: E
|
|
249
|
-
}),
|
|
248
|
+
}), n.tags?.find(({ property: u }) => u === "og:title") || l({
|
|
250
249
|
property: "og:title",
|
|
251
250
|
content: e
|
|
252
|
-
}),
|
|
251
|
+
}), n.tags?.find(({ property: u }) => u === "og:url") || l({
|
|
253
252
|
property: "og:url",
|
|
254
253
|
content: E
|
|
255
254
|
});
|
|
256
255
|
}
|
|
257
|
-
if (n.description && (l({ name: "description", content: n.description }),
|
|
256
|
+
if (n.description && (l({ name: "description", content: n.description }), n.tags?.find(({ property: e }) => e === "og:description") || l({
|
|
258
257
|
property: "og:description",
|
|
259
258
|
content: n.description
|
|
260
259
|
})), (v || n.imageUrl) && l({
|
|
261
260
|
property: "og:image",
|
|
262
261
|
content: n.imageUrl ?? v
|
|
263
|
-
}),
|
|
262
|
+
}), n.tags?.find(({ property: e }) => e === "twitter:card") || l({
|
|
264
263
|
name: "twitter:card",
|
|
265
264
|
content: "summary_large_image"
|
|
266
265
|
}), T && l({
|
|
267
266
|
tag: "link",
|
|
268
267
|
rel: "apple-touch-icon",
|
|
269
268
|
href: T
|
|
270
|
-
}), m && (
|
|
269
|
+
}), m && (q || B) && (l({ name: "prerender-status-code", content: "301" }), l({
|
|
271
270
|
name: "prerender-header",
|
|
272
271
|
content: `Location: ${E}`
|
|
273
272
|
})), 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 =
|
|
273
|
+
const e = G({
|
|
275
274
|
routePattern: n.path,
|
|
276
275
|
currentRoute: r
|
|
277
276
|
}), u = {
|
|
@@ -280,8 +279,8 @@ function N({
|
|
|
280
279
|
routePattern: n.path,
|
|
281
280
|
updateHeadTag: l
|
|
282
281
|
};
|
|
283
|
-
return /* @__PURE__ */ g(
|
|
284
|
-
|
|
282
|
+
return /* @__PURE__ */ g(F.Provider, { value: u, children: /* @__PURE__ */ g(
|
|
283
|
+
H,
|
|
285
284
|
{
|
|
286
285
|
fallback: o,
|
|
287
286
|
errorTitleString: k,
|
|
@@ -319,7 +318,7 @@ function N({
|
|
|
319
318
|
), console.groupEnd()), m && l({ name: "prerender-status-code", content: "500" }), document.title = k, o;
|
|
320
319
|
}
|
|
321
320
|
}
|
|
322
|
-
const
|
|
321
|
+
const J = t.exact({
|
|
323
322
|
path: (i, c, a) => {
|
|
324
323
|
const o = i[c];
|
|
325
324
|
return o == null ? new Error(
|
|
@@ -336,8 +335,8 @@ const ee = t.exact({
|
|
|
336
335
|
tags: t.arrayOf(t.object),
|
|
337
336
|
imageUrl: t.string
|
|
338
337
|
});
|
|
339
|
-
|
|
340
|
-
routes: t.arrayOf(
|
|
338
|
+
K.propTypes = {
|
|
339
|
+
routes: t.arrayOf(J),
|
|
341
340
|
title: t.string,
|
|
342
341
|
separator: t.string,
|
|
343
342
|
notFoundTemplate: t.element,
|
|
@@ -365,9 +364,9 @@ N.propTypes = {
|
|
|
365
364
|
debug: t.bool
|
|
366
365
|
};
|
|
367
366
|
export {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
H as ErrorBoundary,
|
|
368
|
+
K as Routerino,
|
|
369
|
+
K as default,
|
|
371
370
|
l as updateHeadTag,
|
|
372
|
-
|
|
371
|
+
Y as useRouterino
|
|
373
372
|
};
|
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(()=>{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&&(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.4",
|
|
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}": [
|