breeze-router 0.1.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/README.md +48 -0
- package/dist/BreezeRouter.js +274 -0
- package/dist/BreezeRouter.js.map +1 -0
- package/dist/BreezeRouter.min.js +2 -0
- package/dist/BreezeRouter.min.js.map +1 -0
- package/dist/types.js +17 -0
- package/index.js +2 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Breeze Router
|
|
2
|
+
A lightweight, zero-dependency client-side router for single page applications (SPAs).
|
|
3
|
+
|
|
4
|
+
**Note: This project is not production ready and is still in development.**
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To use this router in your project, install the router using npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install breeze-router
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
To use the router in your application, you need to import `BreezeRouter` and define routes and handlers using the `Router` class:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import BeezeRouter from 'breeze-router';
|
|
19
|
+
|
|
20
|
+
// Create a new `BreezeRouter` instance.
|
|
21
|
+
const ROUTER = new BreezeRouter();
|
|
22
|
+
|
|
23
|
+
// Define routes using the `add()` method.
|
|
24
|
+
ROUTER.add('/', async () => {
|
|
25
|
+
// Handle the root route
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
ROUTER.add('/about', async () => {
|
|
29
|
+
// Handle the about route
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
ROUTER.add('/users/:userId', async ({ route, params }) => {
|
|
33
|
+
// Handle the users route with a dynamic parameter :userId
|
|
34
|
+
const userId = params.userId;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
ROUTER.add("/users/:username/posts/:postId", async ({ route, params }) => {
|
|
38
|
+
// Handle the posts route with a dynamic parameter :username and :userId
|
|
39
|
+
const { username, postId } = params;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Call the `start` method to start the router.
|
|
43
|
+
ROUTER.start();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if given param is function
|
|
5
|
+
* @param {Function} fn
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
const isFunction = (fn) => {
|
|
9
|
+
return fn.constructor.name.toLowerCase() === "function";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if given param is async function
|
|
14
|
+
* @param {Function} fn
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
const isAsyncFunction = (fn) => {
|
|
18
|
+
return fn.constructor.name.toLowerCase() === "asyncfunction";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Remove trailing slash of a give url
|
|
23
|
+
* @param {string} url
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
const removeTrailingSlash = (url) => {
|
|
27
|
+
if (url.endsWith("/")) {
|
|
28
|
+
url = url.replace(/\/$/, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return url;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find anchor element from click event.
|
|
36
|
+
* @param {Event} e - The click event.
|
|
37
|
+
* @returns {HTMLAnchorElement|undefined}
|
|
38
|
+
*/
|
|
39
|
+
const findAnchor = (e) => {
|
|
40
|
+
return e.composedPath().find((elem) => {
|
|
41
|
+
return elem.tagName === "A";
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if the router should handle a click event on an anchor element.
|
|
47
|
+
* @param {Event} e - The click event.
|
|
48
|
+
* @param {HTMLAnchorElement} anchor - The anchor element.
|
|
49
|
+
* @returns {boolean} - True if the router should handle the event, false otherwise.
|
|
50
|
+
*/
|
|
51
|
+
const shouldRouterHandleClick = (e, anchor) => {
|
|
52
|
+
// If the event has already been handled by another event listener
|
|
53
|
+
if (e.defaultPrevented) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If the user is holding down the meta, control, or shift key
|
|
58
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!anchor) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (anchor.target) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
anchor.hasAttribute("download") ||
|
|
72
|
+
anchor.getAttribute("rel") === "external"
|
|
73
|
+
) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const href = anchor.href;
|
|
78
|
+
if (!href || href.startsWith("mailto:")) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If the href attribute does not start with the same origin
|
|
83
|
+
if (!href.startsWith(location.origin)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// @ts-check
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Class representing a router.
|
|
94
|
+
*/
|
|
95
|
+
class BreezeRouter {
|
|
96
|
+
/**
|
|
97
|
+
* Creates a new BreezeRouter instance.
|
|
98
|
+
* @constructor
|
|
99
|
+
*/
|
|
100
|
+
constructor() {
|
|
101
|
+
/**
|
|
102
|
+
* Object containing all registered routes.
|
|
103
|
+
* @type {import('./types.js').Route}
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
this._routes = {};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The previous route that was navigated to
|
|
110
|
+
* @type {import('./types.js').Route}
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
this._previousRoute = {};
|
|
114
|
+
|
|
115
|
+
// Bind event listeners
|
|
116
|
+
window.addEventListener("popstate", this._onChanged.bind(this));
|
|
117
|
+
document.body.addEventListener("click", this._handleClick.bind(this));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Starts the router.
|
|
122
|
+
* @returns {void}
|
|
123
|
+
*/
|
|
124
|
+
start() {
|
|
125
|
+
this._onChanged();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Adds a new route to the router.
|
|
130
|
+
* @param {string} route - The route path to add.
|
|
131
|
+
* @param {Function} handler - The async function to handle the route
|
|
132
|
+
* @returns {BreezeRouter|void} The BreezeRouter instance.
|
|
133
|
+
*/
|
|
134
|
+
add(route, handler) {
|
|
135
|
+
route = route.trim();
|
|
136
|
+
if (route !== "/") {
|
|
137
|
+
route = removeTrailingSlash(route.trim());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this._routes[route]) {
|
|
141
|
+
return console.warn(`Route already exists: ${route}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (typeof handler !== "function") {
|
|
145
|
+
return console.error(`handler on route '${route}' is not a function.`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this._routes[route] = {
|
|
149
|
+
path: route,
|
|
150
|
+
handler,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Navigates to the specified URL.
|
|
158
|
+
* @param {string} url - The URL to navigate to
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
161
|
+
navigateTo(url) {
|
|
162
|
+
window.history.pushState({ url }, "", url);
|
|
163
|
+
this._onChanged();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Redirects a URL
|
|
168
|
+
* @param {string} url
|
|
169
|
+
* @returns {void}
|
|
170
|
+
*/
|
|
171
|
+
redirect(url) {
|
|
172
|
+
this.navigateTo(url);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async _onChanged() {
|
|
176
|
+
const path = window.location.pathname;
|
|
177
|
+
const { route, params } = this._matchUrlToRoute(path);
|
|
178
|
+
|
|
179
|
+
// If no matching route found, route will be '404' route
|
|
180
|
+
// which has been handled by _matchUrlToRoute already
|
|
181
|
+
await this._handleRoute({ route, params });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Processes route callbacks registered by app
|
|
186
|
+
* @param {import('./types.js').MatchedRoute} options
|
|
187
|
+
* @returns {Promise<void>}
|
|
188
|
+
*/
|
|
189
|
+
async _handleRoute({ route, params }) {
|
|
190
|
+
if (isFunction(route.handler)) {
|
|
191
|
+
route.handler({ route, params });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (isAsyncFunction(route.handler)) {
|
|
195
|
+
await route.handler({ route, params });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
*
|
|
201
|
+
* @param {string} url - Current url users visite or nagivate to.
|
|
202
|
+
* @returns {import('./types.js').MatchedRoute}
|
|
203
|
+
*/
|
|
204
|
+
_matchUrlToRoute(url) {
|
|
205
|
+
/** @type {import('./types.js').RouteParams} */
|
|
206
|
+
const params = {};
|
|
207
|
+
|
|
208
|
+
if (url !== "/") {
|
|
209
|
+
url = removeTrailingSlash(url);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const matchedRoute = Object.keys(this._routes).find((route) => {
|
|
213
|
+
if (url.split("/").length !== route.split("/").length) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let routeSegments = route.split("/").slice(1);
|
|
218
|
+
let urlSegments = url.split("/").slice(1);
|
|
219
|
+
|
|
220
|
+
// If each segment in the url matches the corresponding segment in the route path,
|
|
221
|
+
// or the route path segment starts with a ':' then the route is matched.
|
|
222
|
+
const match = routeSegments.every((segment, i) => {
|
|
223
|
+
return segment === urlSegments[i] || segment.startsWith(":");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!match) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If the route matches the URL, pull out any params from the URL.
|
|
231
|
+
routeSegments.forEach((segment, i) => {
|
|
232
|
+
if (segment.startsWith(":")) {
|
|
233
|
+
const propName = segment.slice(1);
|
|
234
|
+
params[propName] = decodeURIComponent(urlSegments[i]);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return true;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (matchedRoute) {
|
|
242
|
+
return { route: this._routes[matchedRoute], params };
|
|
243
|
+
} else {
|
|
244
|
+
return { route: this._routes[404], params };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Handles <a> link clicks
|
|
250
|
+
* @param {Event} event
|
|
251
|
+
* @returns {void}
|
|
252
|
+
*/
|
|
253
|
+
_handleClick(event) {
|
|
254
|
+
const anchor = findAnchor(event);
|
|
255
|
+
if (!anchor) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!shouldRouterHandleClick(event, anchor)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
let href = anchor.getAttribute("href").trim();
|
|
265
|
+
if (!href.startsWith("/")) {
|
|
266
|
+
href = "/" + href;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.navigateTo(href);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { BreezeRouter as default };
|
|
274
|
+
//# sourceMappingURL=BreezeRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BreezeRouter.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\").trim();\n if (!href.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const t=t=>(t.endsWith("/")&&(t=t.replace(/\/$/,"")),t);class e{constructor(){this._routes={},this._previousRoute={},window.addEventListener("popstate",this._onChanged.bind(this)),document.body.addEventListener("click",this._handleClick.bind(this))}start(){this._onChanged()}add(e,r){return"/"!==(e=e.trim())&&(e=t(e.trim())),this._routes[e]?console.warn(`Route already exists: ${e}`):"function"!=typeof r?console.error(`handler on route '${e}' is not a function.`):(this._routes[e]={path:e,handler:r},this)}navigateTo(t){window.history.pushState({url:t},"",t),this._onChanged()}redirect(t){this.navigateTo(t)}async _onChanged(){const t=window.location.pathname,{route:e,params:r}=this._matchUrlToRoute(t);await this._handleRoute({route:e,params:r})}async _handleRoute({route:t,params:e}){"function"===t.handler.constructor.name.toLowerCase()&&t.handler({route:t,params:e}),(t=>"asyncfunction"===t.constructor.name.toLowerCase())(t.handler)&&await t.handler({route:t,params:e})}_matchUrlToRoute(e){const r={};"/"!==e&&(e=t(e));const n=Object.keys(this._routes).find((t=>{if(e.split("/").length!==t.split("/").length)return!1;let n=t.split("/").slice(1),a=e.split("/").slice(1);return!!n.every(((t,e)=>t===a[e]||t.startsWith(":")))&&(n.forEach(((t,e)=>{if(t.startsWith(":")){const n=t.slice(1);r[n]=decodeURIComponent(a[e])}})),!0)}));return n?{route:this._routes[n],params:r}:{route:this._routes[404],params:r}}_handleClick(t){const e=t.composedPath().find((t=>"A"===t.tagName));if(!e)return;if(!((t,e)=>{if(t.defaultPrevented)return!1;if(t.metaKey||t.ctrlKey||t.shiftKey)return!1;if(!e)return!1;if(e.target)return!1;if(e.hasAttribute("download")||"external"===e.getAttribute("rel"))return!1;const r=e.href;return!(!r||r.startsWith("mailto:")||!r.startsWith(location.origin))})(t,e))return;t.preventDefault();let r=e.getAttribute("href").trim();r.startsWith("/")||(r="/"+r),this.navigateTo(r)}}export{e as default};
|
|
2
|
+
//# sourceMappingURL=BreezeRouter.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BreezeRouter.min.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\").trim();\n if (!href.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n}\n"],"names":["removeTrailingSlash","url","endsWith","replace","BreezeRouter","constructor","this","_routes","_previousRoute","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","add","route","handler","trim","console","warn","error","path","navigateTo","history","pushState","redirect","async","location","pathname","params","_matchUrlToRoute","_handleRoute","name","toLowerCase","fn","isAsyncFunction","matchedRoute","Object","keys","find","split","length","routeSegments","slice","urlSegments","every","segment","i","startsWith","forEach","propName","decodeURIComponent","event","anchor","composedPath","elem","tagName","e","defaultPrevented","metaKey","ctrlKey","shiftKey","target","hasAttribute","getAttribute","href","origin","shouldRouterHandleClick","preventDefault"],"mappings":"AAOO,MAkBMA,EAAuBC,IAC9BA,EAAIC,SAAS,OACfD,EAAMA,EAAIE,QAAQ,MAAO,KAGpBF,GClBM,MAAMG,EAKnBC,cAMEC,KAAKC,QAAU,GAOfD,KAAKE,eAAiB,GAGtBC,OAAOC,iBAAiB,WAAYJ,KAAKK,WAAWC,KAAKN,OACzDO,SAASC,KAAKJ,iBAAiB,QAASJ,KAAKS,aAAaH,KAAKN,MAChE,CAMDU,QACEV,KAAKK,YACN,CAQDM,IAAIC,EAAOC,GAMT,MAJc,OADdD,EAAQA,EAAME,UAEZF,EAAQlB,EAAoBkB,EAAME,SAGhCd,KAAKC,QAAQW,GACRG,QAAQC,KAAK,yBAAyBJ,KAGxB,mBAAZC,EACFE,QAAQE,MAAM,qBAAqBL,0BAG5CZ,KAAKC,QAAQW,GAAS,CACpBM,KAAMN,EACNC,WAGKb,KACR,CAODmB,WAAWxB,GACTQ,OAAOiB,QAAQC,UAAU,CAAE1B,OAAO,GAAIA,GACtCK,KAAKK,YACN,CAODiB,SAAS3B,GACPK,KAAKmB,WAAWxB,EACjB,CAED4B,mBACE,MAAML,EAAOf,OAAOqB,SAASC,UACvBb,MAAEA,EAAKc,OAAEA,GAAW1B,KAAK2B,iBAAiBT,SAI1ClB,KAAK4B,aAAa,CAAEhB,QAAOc,UAClC,CAODH,oBAAmBX,MAAEA,EAAKc,OAAEA,IDlGiB,aCmG5Bd,EAAMC,QDnGbd,YAAY8B,KAAKC,eCoGvBlB,EAAMC,QAAQ,CAAED,QAAOc,WD5FE,CAACK,GACe,kBAAtCA,EAAGhC,YAAY8B,KAAKC,cC8FrBE,CAAgBpB,EAAMC,gBAClBD,EAAMC,QAAQ,CAAED,QAAOc,UAEhC,CAODC,iBAAiBhC,GAEf,MAAM+B,EAAS,CAAA,EAEH,MAAR/B,IACFA,EAAMD,EAAoBC,IAG5B,MAAMsC,EAAeC,OAAOC,KAAKnC,KAAKC,SAASmC,MAAMxB,IACnD,GAAIjB,EAAI0C,MAAM,KAAKC,SAAW1B,EAAMyB,MAAM,KAAKC,OAC7C,OAAO,EAGT,IAAIC,EAAgB3B,EAAMyB,MAAM,KAAKG,MAAM,GACvCC,EAAc9C,EAAI0C,MAAM,KAAKG,MAAM,GAQvC,QAJcD,EAAcG,OAAM,CAACC,EAASC,IACnCD,IAAYF,EAAYG,IAAMD,EAAQE,WAAW,SAQ1DN,EAAcO,SAAQ,CAACH,EAASC,KAC9B,GAAID,EAAQE,WAAW,KAAM,CAC3B,MAAME,EAAWJ,EAAQH,MAAM,GAC/Bd,EAAOqB,GAAYC,mBAAmBP,EAAYG,GACnD,MAGI,EAAI,IAGb,OAAIX,EACK,CAAErB,MAAOZ,KAAKC,QAAQgC,GAAeP,UAErC,CAAEd,MAAOZ,KAAKC,QAAQ,KAAMyB,SAEtC,CAODjB,aAAawC,GACX,MAAMC,EAAoBD,EDpInBE,eAAef,MAAMgB,GACJ,MAAjBA,EAAKC,UCoIZ,IAAKH,EACH,OAGF,ID9HmC,EAACI,EAAGJ,KAEzC,GAAII,EAAEC,iBACJ,OAAO,EAIT,GAAID,EAAEE,SAAWF,EAAEG,SAAWH,EAAEI,SAC9B,OAAO,EAGT,IAAKR,EACH,OAAO,EAGT,GAAIA,EAAOS,OACT,OAAO,EAGT,GACET,EAAOU,aAAa,aACW,aAA/BV,EAAOW,aAAa,OAEpB,OAAO,EAGT,MAAMC,EAAOZ,EAAOY,KACpB,SAAKA,GAAQA,EAAKjB,WAAW,aAKxBiB,EAAKjB,WAAWrB,SAASuC,QAInB,EC0FJC,CAAwBf,EAAOC,GAClC,OAGFD,EAAMgB,iBACN,IAAIH,EAAOZ,EAAOW,aAAa,QAAQ/C,OAClCgD,EAAKjB,WAAW,OACnBiB,EAAO,IAAMA,GAGf9D,KAAKmB,WAAW2C,EACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Route
|
|
3
|
+
* @property {string} path - The path of the route
|
|
4
|
+
* @property {Function} handler - The handler function for the route.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object.<string, string>} RouteParams
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} MatchedRoute
|
|
13
|
+
* @property {Route} route
|
|
14
|
+
* @property {RouteParams} params
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export { Route, RouteParams, MatchedRoute };
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "breeze-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight, zero-dependency client-side router for single page applications (SPAs).",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test:dev": "rollup -c & pnpm run -r dev",
|
|
13
|
+
"dev": "vite",
|
|
14
|
+
"build": "rollup -c",
|
|
15
|
+
"prepare": "husky install"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/heybran/breeze-spa-router.git"
|
|
20
|
+
},
|
|
21
|
+
"author": "Brandon Zhang",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/heybran/breeze-spa-router/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/heybran/breeze-spa-router#readme",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"Router",
|
|
29
|
+
"JavaScript",
|
|
30
|
+
"Frontend",
|
|
31
|
+
"Spa",
|
|
32
|
+
"Vanilla"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@rollup/plugin-terser": "^0.4.3",
|
|
36
|
+
"husky": "^8.0.0",
|
|
37
|
+
"prettier": "^2.8.8",
|
|
38
|
+
"pretty-quick": "^3.1.3",
|
|
39
|
+
"rollup": "^3.23.0",
|
|
40
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
41
|
+
"vite": "^4.3.9"
|
|
42
|
+
}
|
|
43
|
+
}
|