lightweight-router 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ MIT License
2
+ ... (standard MIT license text here)
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # lightweight-router
2
+
3
+ A lightweight client-side router with prefetching capabilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install lightweight-router
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Refer to the documentation for more details.
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "lightweight-router",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "",
10
+ "license": "ISC",
11
+ "description": ""
12
+ }
File without changes
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { initializeRouter, setRouteChangeHandler } from './router';
2
+
3
+ export const startRouter = (options = {}) => {
4
+ const { onRouteChange } = options;
5
+ if (onRouteChange) setRouteChangeHandler(onRouteChange);
6
+ initializeRouter();
7
+ };
package/src/router.js ADDED
@@ -0,0 +1,96 @@
1
+ import { fetchContent } from './utils';
2
+
3
+ let linkData = new Proxy({}, {
4
+ set: (target, property, value) => {
5
+ target[property] = value;
6
+ const router = document.querySelector("router");
7
+ const currentRoute = router?.querySelector(`route[path="${globalThis.location.pathname}"]`) ||
8
+ router?.querySelector(`route[path="/default"]`);
9
+
10
+ if (currentRoute && !currentRoute.innerHTML && property === globalThis.location.href) {
11
+ currentRoute.innerHTML = value;
12
+ if (typeof onRouteChange === 'function') onRouteChange(currentRoute);
13
+ }
14
+ return true;
15
+ },
16
+ });
17
+
18
+ let onRouteChange;
19
+
20
+ export const setRouteChangeHandler = (handler) => {
21
+ onRouteChange = handler;
22
+ };
23
+
24
+ const fetchAndSaveContent = async (link) => {
25
+ linkData[link.href] = "";
26
+ linkData[link.href] = await fetchContent(link.href);
27
+ };
28
+
29
+ const handleLinkHover = async (event) => {
30
+ const link = event.target;
31
+ if (!linkData[link.href]) await fetchAndSaveContent(link);
32
+ };
33
+
34
+ const handleLinkIntersection = (entries, observer) => {
35
+ entries.forEach(entry => {
36
+ if (entry.isIntersecting) {
37
+ const link = entry.target;
38
+ if (!linkData[link.href]) {
39
+ fetchAndSaveContent(link);
40
+ observer.unobserve(link);
41
+ }
42
+ }
43
+ });
44
+ };
45
+
46
+ const handlePopState = async () => {
47
+ const router = document.querySelector("router");
48
+ if (!router) return;
49
+
50
+ const currentRoute = router.querySelector(`route[path="${globalThis.location.pathname}"]`) ||
51
+ router.querySelector(`route[path="/default"]`);
52
+
53
+ router.querySelectorAll("route").forEach(route => (route.style.display = "none"));
54
+ currentRoute.style.display = "contents";
55
+
56
+ if (!currentRoute.innerHTML) {
57
+ currentRoute.innerHTML = await linkData[globalThis.location.href];
58
+ if (typeof onRouteChange === 'function') onRouteChange(currentRoute);
59
+ }
60
+ };
61
+
62
+ const handleLinkClick = (e) => {
63
+ if (e.target.tagName === "A" && e.target.href) {
64
+ const href = new URL(e.target.href).pathname;
65
+ if (href.startsWith("/")) {
66
+ e.preventDefault();
67
+ globalThis.history.pushState(null, null, e.target.href);
68
+ globalThis.dispatchEvent(new Event("popstate"));
69
+ }
70
+ }
71
+ };
72
+
73
+ export const observeLinks = (observer) => {
74
+ const saveDataOn = navigator.connection && navigator.connection.saveData;
75
+ const links = document.querySelectorAll("a");
76
+
77
+ links.forEach(link => {
78
+ if (link.getAttribute("prefetch") !== "onHover" && !saveDataOn) observer.observe(link);
79
+ });
80
+ };
81
+
82
+ export const initializeRouter = () => {
83
+ if (typeof document === "undefined") return;
84
+
85
+ globalThis.addEventListener("popstate", handlePopState);
86
+ document.addEventListener("click", handleLinkClick);
87
+
88
+ document.body.addEventListener("mouseover", (event) => {
89
+ if (event.target.tagName === "A" && event.target.getAttribute("prefetch") === "onHover") {
90
+ handleLinkHover(event);
91
+ }
92
+ });
93
+
94
+ const observer = new IntersectionObserver(handleLinkIntersection, { root: null, threshold: 0.5 });
95
+ observeLinks(observer);
96
+ };
package/src/utils.js ADDED
@@ -0,0 +1,20 @@
1
+ export const getPathname = () => {
2
+ const { pathname } = globalThis.location;
3
+ return pathname.startsWith("/") ? pathname.slice(1) : pathname;
4
+ };
5
+
6
+ export const fetchContent = async (url) => {
7
+ const response = await fetch(url, { method: "POST", body: "onlyRoute" });
8
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
9
+
10
+ const reader = response.body.getReader();
11
+ const decoder = new TextDecoder("utf-8");
12
+ let content = "";
13
+
14
+ while (true) {
15
+ const { done, value } = await reader.read();
16
+ if (done) break;
17
+ content += decoder.decode(value);
18
+ }
19
+ return content;
20
+ };