router-dom
Advanced tools
Comparing version 1.2.1 to 2.0.0
@@ -6,2 +6,3 @@ export default class Router { | ||
constructor(routes: [RouteParam, ...RouteParam[]], options?: Options); | ||
private getMatchingRoute; | ||
private doRouting; | ||
@@ -13,3 +14,3 @@ go(path: string, state: LooseObject, params?: string): void; | ||
changeOptions(options: Options): void; | ||
getParams(search?: string): { | ||
static getParams(search?: string): { | ||
[k: string]: string; | ||
@@ -44,3 +45,3 @@ }; | ||
to: string; | ||
state: LooseObject; | ||
state?: LooseObject; | ||
params?: LooseObject; | ||
@@ -47,0 +48,0 @@ } |
@@ -1,9 +0,7 @@ | ||
import { listen } from "quicklink"; | ||
import { pathToRegexp } from "path-to-regexp"; | ||
import { pathToRegexp, match } from "path-to-regexp"; | ||
import { render, html, hydro, $, $$ } from "hydro-js"; | ||
listen(); | ||
let router; | ||
const outletSelector = "[data-outlet]"; | ||
const reactivityRegex = /\{\{([^]*?)\}\}/; | ||
const flagsRegex = /:\w+/g; | ||
const fetchCache = new WeakMap(); | ||
let base = $("base")?.getAttribute("href") || ""; | ||
@@ -14,3 +12,3 @@ if (base.endsWith("/")) { | ||
addEventListener("popstate", async (e) => { | ||
//@ts-ignore | ||
//@ts-expect-error | ||
router.doRouting(location.pathname, e); | ||
@@ -30,19 +28,48 @@ }); | ||
router = this; | ||
// Prefetch resources | ||
this.routes.forEach((route) => { | ||
//@ts-expect-error | ||
if (route.templateUrl && !navigator.connection?.saveData) { | ||
const controller = new AbortController(); | ||
const cache = { promise: null, controller }; | ||
fetchCache.set(route, cache); | ||
//@ts-expect-error | ||
requestIdleCallback(() => { | ||
cache.promise = fetch(route.templateUrl, { | ||
signal: controller.signal, | ||
}); | ||
cache.promise | ||
.then((res) => res.text()) | ||
.then((_html) => { | ||
cache.html = _html; | ||
}) | ||
.catch(async (err) => { | ||
await this.options.errorHandler?.(err); | ||
}); | ||
}); | ||
} | ||
}); | ||
this.doRouting(); | ||
} | ||
getMatchingRoute(path) { | ||
if (path.startsWith(".")) { | ||
path = path.replace(".", ""); | ||
} | ||
return this.routes.find((route) => route.path.exec(path)); | ||
} | ||
async doRouting(to = location.pathname, e) { | ||
dispatchEvent(new Event("beforeRouting")); | ||
const from = this.oldRoute ?? to; | ||
const route = getMatchingRoute(to); | ||
const route = this.getMatchingRoute(to); | ||
if (route) { | ||
try { | ||
const [_, ...values] = to.match(route.path); | ||
const params = Array.from(route.originalPath.matchAll(flagsRegex)) | ||
.flat() | ||
.map((i) => i.replace(":", "")) | ||
.reduce((state, key, idx) => { | ||
state[key] = values[idx]; | ||
return state; | ||
}, {}); | ||
const allParams = { ...this.getParams(), ...params }; | ||
const { params } = match(route.originalPath, { | ||
decode: decodeURIComponent, | ||
})(to); | ||
const allParams = { | ||
...Router.getParams(), | ||
...Object.fromEntries(Object.entries(params) | ||
.map((pair) => Number.isNaN(Number(pair[0])) && pair) | ||
.filter(Boolean)), | ||
}; | ||
const props = { | ||
@@ -52,3 +79,5 @@ from: from.replace(base, ""), | ||
...(Object.keys(allParams).length ? { params: allParams } : {}), | ||
...history.state, | ||
...(history.state && Object.keys(history.state).length | ||
? { state: history.state } | ||
: {}), | ||
}; | ||
@@ -67,5 +96,18 @@ // Trigger leave | ||
if (route?.templateUrl) { | ||
const data = await fetch(route.templateUrl); | ||
const _html = await data.text(); | ||
render(html `<div data-outlet>${_html}</div>`, outletSelector, false); | ||
let cacheObj = fetchCache.get(route); | ||
if (!fetchCache.has(route) || cacheObj?.promise === null) { | ||
cacheObj.controller?.abort(); | ||
const data = await fetch(route.templateUrl); | ||
if (!cacheObj) { | ||
cacheObj = { | ||
html: await data.text(), | ||
}; | ||
fetchCache.set(route, cacheObj); | ||
} | ||
else { | ||
cacheObj.html = await data.text(); | ||
} | ||
} | ||
Reflect.deleteProperty(cacheObj, "controller"); | ||
render(html `<div data-outlet>${await cacheObj.html}</div>`, outletSelector, false); | ||
} | ||
@@ -130,12 +172,6 @@ else if (route?.element) { | ||
} | ||
getParams(search = location.search) { | ||
static getParams(search = location.search) { | ||
return Object.fromEntries(new URLSearchParams(search)); | ||
} | ||
} | ||
function getMatchingRoute(path) { | ||
if (path.startsWith(".")) { | ||
path = path.replace(".", ""); | ||
} | ||
return router.routes.find((route) => route.path.exec(path)); | ||
} | ||
function registerAnchorEvent(anchor) { | ||
@@ -148,3 +184,3 @@ if (anchor.getAttribute("href")?.startsWith("http")) | ||
const hydroProp = replaceBars(hasData); | ||
let href = anchor.getAttribute("data-href") || anchor.getAttribute("href") || ""; | ||
const href = anchor.getAttribute("href") || ""; | ||
router.go(href, hasData ? hydro[hydroProp] : void 0); | ||
@@ -151,0 +187,0 @@ }); |
{ | ||
"name": "router-dom", | ||
"version": "1.2.1", | ||
"version": "2.0.0", | ||
"description": "A lightweight router for everyone", | ||
@@ -41,5 +41,4 @@ "type": "module", | ||
"hydro-js": "^1.4.1", | ||
"path-to-regexp": "^6.2.0", | ||
"quicklink": "^2.1.0" | ||
"path-to-regexp": "^6.2.0" | ||
} | ||
} |
# router-dom | ||
> A lightweight router for single-page applications. | ||
> A lightweight router for single-page applications with faster subsequent page-loads by prefetching links during idle time if the user is not saving data. | ||
> | ||
@@ -11,2 +11,3 @@ > - it helps to reduce the delay between your pages, to minimize browser HTTP requests and enhance your user's web experience. | ||
> - support in all modern browsers. | ||
> - RegExp Routes | ||
@@ -31,2 +32,3 @@ ## Demo | ||
import Router from "https://cdn.skypack.dev/router-dom"; | ||
new Router(...) // see Constructor Documentation | ||
</script> | ||
@@ -37,7 +39,6 @@ ``` | ||
Use the href attribute in order to help `quicklink` prefetching the resource and use data-href as routing path. | ||
```html | ||
<a href="/">Home</a> | ||
<a href="about.html" data-href="/about">About</a> | ||
<a href="/about">About</a> | ||
<div data-outlet></div> | ||
@@ -49,3 +50,2 @@ ``` | ||
[path-to-regexp](https://github.com/pillarjs/path-to-regexp): Turn a path string such as '/user/:name' into a regular expression<br> | ||
[quicklink](https://github.com/GoogleChromeLabs/quicklink): Faster subsequent page-loads by prefetching in-viewport links during idle time <br> | ||
[hydro-js](https://github.com/Krutsch/hydro-js): Renders the view. In order to pass state via an anchor element (data attribute), a mapping on the hydro object is needed.<br> | ||
@@ -71,3 +71,3 @@ | ||
templateUrl: "/about.html", | ||
leave: ({ from, to, state, params }) => {}, | ||
leave: ({ from, to, params, state }) => {}, | ||
}, | ||
@@ -77,4 +77,4 @@ { | ||
element: html`<h2>Drop a message on [...]</h2>`, | ||
beforeEnter: ({ from, to, state, params }) => {}, | ||
afterEnter: ({ from, to, state, params }) => {}, | ||
beforeEnter: ({ from, to, params, state }) => {}, | ||
afterEnter: ({ from, to, params, state }) => {}, | ||
}, | ||
@@ -104,8 +104,4 @@ ]); | ||
### getParams | ||
### static getParams | ||
- Returns the params as key-value pair. | ||
## To Do | ||
- Add nested routes |
@@ -1,11 +0,9 @@ | ||
import { listen } from "quicklink"; | ||
import { pathToRegexp } from "path-to-regexp"; | ||
import type { MatchResult } from "path-to-regexp"; | ||
import { pathToRegexp, match } from "path-to-regexp"; | ||
import { render, html, hydro, $, $$ } from "hydro-js"; | ||
listen(); | ||
let router: Router; | ||
const outletSelector = "[data-outlet]"; | ||
const reactivityRegex = /\{\{([^]*?)\}\}/; | ||
const flagsRegex = /:\w+/g; | ||
const fetchCache = new WeakMap<Route, Cache>(); | ||
let base = $("base")?.getAttribute("href") || ""; | ||
@@ -17,3 +15,3 @@ if (base.endsWith("/")) { | ||
addEventListener("popstate", async (e) => { | ||
//@ts-ignore | ||
//@ts-expect-error | ||
router.doRouting(location.pathname, e); | ||
@@ -38,23 +36,56 @@ }); | ||
this.options = options; | ||
router = this; | ||
// Prefetch resources | ||
this.routes.forEach((route) => { | ||
//@ts-expect-error | ||
if (route.templateUrl && !navigator.connection?.saveData) { | ||
const controller = new AbortController(); | ||
const cache = { promise: null, controller } as Cache; | ||
fetchCache.set(route, cache); | ||
//@ts-expect-error | ||
requestIdleCallback(() => { | ||
cache.promise = fetch(route.templateUrl!, { | ||
signal: controller.signal, | ||
}); | ||
(cache.promise as unknown as Promise<Response>) | ||
.then((res) => res.text()) | ||
.then((_html) => { | ||
cache.html = _html; | ||
}) | ||
.catch(async (err) => { | ||
await this.options.errorHandler?.(err); | ||
}); | ||
}); | ||
} | ||
}); | ||
this.doRouting(); | ||
} | ||
private getMatchingRoute(path: string): Route | undefined { | ||
if (path.startsWith(".")) { | ||
path = path.replace(".", ""); | ||
} | ||
return this.routes.find((route) => route.path.exec(path)); | ||
} | ||
private async doRouting(to: string = location.pathname, e?: PopStateEvent) { | ||
dispatchEvent(new Event("beforeRouting")); | ||
const from = this.oldRoute ?? to; | ||
const route = getMatchingRoute(to); | ||
const route = this.getMatchingRoute(to); | ||
if (route) { | ||
try { | ||
const [_, ...values] = to.match(route.path)!; | ||
const params = Array.from(route.originalPath.matchAll(flagsRegex)) | ||
.flat() | ||
.map((i) => i.replace(":", "")) | ||
.reduce((state: LooseObject, key, idx) => { | ||
state[key] = values[idx]; | ||
return state; | ||
}, {}); | ||
const allParams = { ...this.getParams(), ...params }; | ||
const { params } = match(route.originalPath, { | ||
decode: decodeURIComponent, | ||
})(to) as MatchResult; | ||
const allParams = { | ||
...Router.getParams(), | ||
...Object.fromEntries( | ||
Object.entries(params) | ||
.map((pair) => Number.isNaN(Number(pair[0])) && pair) | ||
.filter(Boolean) as Iterable<[string | symbol, string]> | ||
), | ||
}; | ||
const props = { | ||
@@ -64,3 +95,5 @@ from: from.replace(base, ""), | ||
...(Object.keys(allParams).length ? { params: allParams } : {}), | ||
...history.state, | ||
...(history.state && Object.keys(history.state).length | ||
? { state: history.state } | ||
: {}), | ||
}; | ||
@@ -84,5 +117,23 @@ | ||
if (route?.templateUrl) { | ||
const data = await fetch(route.templateUrl); | ||
const _html = await data.text(); | ||
render(html`<div data-outlet>${_html}</div>`, outletSelector, false); | ||
let cacheObj = fetchCache.get(route); | ||
if (!fetchCache.has(route) || cacheObj?.promise === null) { | ||
cacheObj!.controller?.abort(); | ||
const data = await fetch(route.templateUrl); | ||
if (!cacheObj) { | ||
cacheObj = { | ||
html: await data.text(), | ||
}; | ||
fetchCache.set(route, cacheObj); | ||
} else { | ||
cacheObj.html = await data.text(); | ||
} | ||
} | ||
Reflect.deleteProperty(cacheObj!, "controller"); | ||
render( | ||
html`<div data-outlet>${await cacheObj!.html}</div>`, | ||
outletSelector, | ||
false | ||
); | ||
} else if (route?.element) { | ||
@@ -158,3 +209,3 @@ render( | ||
getParams(search = location.search) { | ||
static getParams(search = location.search) { | ||
return Object.fromEntries(new URLSearchParams(search)); | ||
@@ -164,9 +215,2 @@ } | ||
function getMatchingRoute(path: string): Route | undefined { | ||
if (path.startsWith(".")) { | ||
path = path.replace(".", ""); | ||
} | ||
return router.routes.find((route) => route.path.exec(path)); | ||
} | ||
function registerAnchorEvent(anchor: HTMLAnchorElement) { | ||
@@ -178,4 +222,3 @@ if (anchor.getAttribute("href")?.startsWith("http")) return; | ||
const hydroProp = replaceBars(hasData); | ||
let href = | ||
anchor.getAttribute("data-href") || anchor.getAttribute("href") || ""; | ||
const href = anchor.getAttribute("href") || ""; | ||
router.go(href, hasData ? hydro[hydroProp!] : void 0); | ||
@@ -270,5 +313,10 @@ }); | ||
to: string; | ||
state: LooseObject; | ||
state?: LooseObject; | ||
params?: LooseObject; | ||
} | ||
type LooseObject = Record<keyof any, any>; | ||
type Cache = { | ||
promise?: null | Promise<Response>; | ||
controller?: AbortController; | ||
html?: string; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
49200
2
582
101
3
- Removedquicklink@^2.1.0
- Removedjs-tokens@4.0.0(transitive)
- Removedloose-envify@1.4.0(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedprop-types@15.8.1(transitive)
- Removedquicklink@2.3.0(transitive)
- Removedreact@16.14.0(transitive)
- Removedreact-dom@16.14.0(transitive)
- Removedreact-is@16.13.1(transitive)
- Removedregexparam@1.3.0(transitive)
- Removedroute-manifest@1.0.0(transitive)
- Removedscheduler@0.19.1(transitive)
- Removedthrottles@1.0.1(transitive)