Created
July 4, 2025 20:56
-
-
Save robert-hoffmann/48ae4a5b112af5181abab5da8e37dabf to your computer and use it in GitHub Desktop.
VanillaJS (VueRouter style) Router
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const MyRouter = (function(win) { | |
| // Create a safe default route object | |
| const createSafeRoute = (routeData = null) => { | |
| if (!routeData) { | |
| return { | |
| query : {}, | |
| path : "", | |
| params: {} | |
| }; | |
| } | |
| return { | |
| query : routeData.query || {}, | |
| path : routeData.path || "", | |
| params: routeData.params || {} | |
| }; | |
| }; | |
| let currentRoute = createSafeRoute(); | |
| let previousRoute = createSafeRoute(); | |
| let routeHandler = null; | |
| function handleRoute(e) { | |
| const newRoute = e.detail; | |
| // Update route history with safe objects | |
| previousRoute = createSafeRoute(currentRoute); | |
| currentRoute = createSafeRoute(newRoute); | |
| // Call handler with Vue-style (to, from) pattern | |
| if (routeHandler) { | |
| routeHandler(currentRoute, previousRoute); | |
| } else { | |
| console.log('Route changed:', { to: currentRoute, from: previousRoute }); | |
| } | |
| } | |
| win.addEventListener("routeEvent", handleRoute); | |
| // Constants - declared once | |
| const PLUS_REGEX = /\+/g; | |
| const PARAM_REGEX = /([^&=]+)=?([^&]*)/g; | |
| // Utility functions | |
| const decode = (s) => { | |
| try { | |
| return decodeURIComponent(s.replace(PLUS_REGEX, " ")); | |
| } catch (e) { | |
| console.warn('Failed to decode URL parameter:', s, e); | |
| return s; // Return original string if decode fails | |
| } | |
| }; | |
| const parseParams = (queryString) => { | |
| const params = {}; | |
| if (!queryString) return params; | |
| const matches = queryString.matchAll(PARAM_REGEX); | |
| for (const match of matches) { | |
| const key = decode(match[1]); | |
| const value = decode(match[2]); | |
| params[key] = value; // Always add - decode never returns null | |
| } | |
| return params; | |
| }; | |
| const parseRoute = () => { | |
| const query = win.location.search.substring(1); | |
| const hash = win.location.hash.slice(1); | |
| const hashQueryIndex = hash.indexOf("?"); | |
| let path = hash; | |
| let hashParams = {}; | |
| // Handle hash with query parameters | |
| if (hashQueryIndex !== -1) { | |
| path = hash.substring(0, hashQueryIndex); | |
| const hashQuery = hash.substring(hashQueryIndex + 1); | |
| hashParams = parseParams(hashQuery); | |
| } | |
| return { | |
| query : parseParams(query), | |
| path : path, | |
| params: hashQueryIndex === -1 ? parseParams(query) : hashParams | |
| }; | |
| }; | |
| const handleRouteChange = () => { | |
| const router = parseRoute(); | |
| win.dispatchEvent(new CustomEvent("routeEvent", { detail: router })); | |
| }; | |
| // Set up event listener | |
| win.addEventListener('popstate', handleRouteChange); | |
| // Initial route parsing | |
| handleRouteChange(); | |
| return { | |
| // Vue Router style method | |
| beforeEach: (handler) => { | |
| if (typeof handler === 'function') { | |
| routeHandler = handler; | |
| } | |
| }, | |
| // Navigation methods (Vue Router style) | |
| push: (path) => { | |
| win.location.hash = path; | |
| }, | |
| replace: (path) => { | |
| win.location.replace(win.location.pathname + win.location.search + '#' + path); | |
| }, | |
| // Always returns safe {to, from} object | |
| getRouteState: () => ({ | |
| to : createSafeRoute(currentRoute), | |
| from: createSafeRoute(previousRoute) | |
| }), | |
| // Returns just current route | |
| currentRoute: () => createSafeRoute(currentRoute), | |
| // Returns just previous route | |
| previousRoute: () => createSafeRoute(previousRoute), | |
| // Go back/forward | |
| go: (n) => { | |
| win.history.go(n); | |
| }, | |
| back: () => { | |
| win.history.back(); | |
| }, | |
| forward: () => { | |
| win.history.forward(); | |
| } | |
| }; | |
| })(window); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is actually a old router i made, but decided to make do some brainstorming with Sonnet 4, and see if i can make it better:
So now it has behavior like VueRouter: beforeEach(to, from), and also various methods
You can see the full docs here:
https://claude.ai/public/artifacts/c1ea2981-b655-4366-ba3e-1f669cf43d52