Skip to content

Commit

Permalink
Add hash to router
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Oct 20, 2024
1 parent d293d80 commit 5ae0315
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 37 deletions.
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
A tiny URL router for [Nano Stores](https://github.com/nanostores/nanostores)
state manager.

- **Small.** 684 bytes (minified and brotlied). Zero dependencies.
- **Small.** 712 bytes (minified and brotlied). Zero dependencies.
- Good **TypeScript** support.
- Framework agnostic. Can be used with **React**, **Preact**, **Vue**,
**Svelte**, **Angular**, **Solid.js**, and vanilla JS.
Expand Down Expand Up @@ -120,12 +120,14 @@ router.get() //=> {
// path: '/posts/general',
// route: 'list',
// params: { category: 'general' },
// search: { sort: 'name' }
// search: { sort: 'name' },
// hash: ''
// }
```

To disable the automatic parsing of search params in routes you need to set `search` option.
Router will now treat search query like `?a=1&b=2` as a string. Parameters order will be critical.
To disable the automatic parsing of search params in routes you need
to set `search` option. Router will now treat search query like `?a=1&b=2`
as a string. Parameters order will be critical.

```js
createRouter({ home: '/posts?page=general' }, { search: true })
Expand All @@ -135,7 +137,24 @@ router.get() //=> {
// path: '/posts?page=general',
// route: 'list',
// params: { },
// search: { }
// search: { },
// hash: ''
// }
```

### Hash Routing

Router’s value has current `location.hash` and router updates its value
on hash changes.

```js
location.href = '/posts/general#dialog'
router.get() //=> {
// path: '/posts/general',
// route: 'list',
// params: { category: 'general' },
// search: {},
// hash: '#dialog'
// }
```

Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export type Page<
PageName extends keyof Config = any
> = PageName extends any
? {
hash: string
params: ParamsFromConfig<Config>[PageName]
path: string
route: PageName
Expand Down
58 changes: 31 additions & 27 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,36 @@ export function createRouter(routes, opts = {}) {
.replace(/\/\\:(\w+)\\\?/g, '(?:/(?<$1>(?<=/)[^/]+))?')
.replace(/\/\\:(\w+)/g, '/(?<$1>[^/]+)')

return [
name,
RegExp('^' + pattern + '$', 'i'),
null,
value
];
return [name, RegExp('^' + pattern + '$', 'i'), null, value]
})

let prev
let parse = path => {
path = path.replace(/\/($|\?)/, '$1') || '/'
if (prev === path) return false
prev = path
let parse = href => {
let url = new URL(href.replace(/#$/, ''), 'http://a')
let cache = url.pathname + url.search + url.hash
if (prev === cache) return false
prev = cache

let url = new URL(path, 'http://a')
if (!opts.search) path = url.pathname
let path = opts.search ? url.pathname + url.search : url.pathname
path = path.replace(/\/($|\?)/, '$1') || '/'

for (let [route, pattern, callback] of router.routes) {
let match = path.match(pattern)
if (match) {
return {
// If route has callback for params decoding use it. Otherwise decode params from named capture groups
params: callback ? callback(...match.slice(1)) : Object.keys({...match.groups}).reduce((pars, key) => {
// match === undefined when nothing captured in regexp group
// and we swap it with empty string for backward compatibility
pars[key] = match.groups[key] ? decodeURIComponent(match.groups[key]) : '';
return pars
}, {}),
hash: url.hash,
// If route has callback for params decoding use it.
// Otherwise decode params from named capture groups
params: callback
? callback(...match.slice(1))
: Object.keys({ ...match.groups }).reduce((pars, key) => {
// match === undefined when nothing captured in regexp group
// and we swap it with empty string for backward compatibility
pars[key] = match.groups[key]
? decodeURIComponent(match.groups[key])
: ''
return pars
}, {}),
path,
route,
search: Object.fromEntries(url.searchParams)
Expand All @@ -69,9 +71,9 @@ export function createRouter(routes, opts = {}) {
!event.defaultPrevented // Click was not cancelled
) {
event.preventDefault()
let changed = location.hash !== link.hash
router.open(link.pathname + link.search)
if (changed) {
let hashChanged = location.hash !== link.hash
router.open(link.href)
if (hashChanged) {
location.hash = link.hash
if (link.hash === '' || link.hash === '#') {
window.dispatchEvent(new HashChangeEvent('hashchange'))
Expand All @@ -85,21 +87,23 @@ export function createRouter(routes, opts = {}) {
delete router.set
}

let popstate = () => {
let page = parse(location.pathname + location.search)
let change = () => {
let page = parse(location.href)
if (page !== false) set(page)
}

if (typeof window !== 'undefined' && typeof location !== 'undefined') {
onMount(router, () => {
let page = parse(location.pathname + location.search)
let page = parse(location.href)
if (page !== false) set(page)
if (opts.links !== false) document.body.addEventListener('click', click)
window.addEventListener('popstate', popstate)
window.addEventListener('popstate', change)
window.addEventListener('hashchange', change)
return () => {
prev = undefined
document.body.removeEventListener('click', click)
window.removeEventListener('popstate', popstate)
window.removeEventListener('popstate', change)
window.removeEventListener('hashchange', change)
}
})
} else {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@nanostores/router",
"version": "0.15.1",
"description": "A tiny (684 bytes) router for Nano Stores state manager",
"description": "A tiny (712 bytes) router for Nano Stores state manager",
"keywords": [
"nano",
"router",
Expand Down Expand Up @@ -85,7 +85,7 @@
"import": {
"./index.js": "{ createRouter }"
},
"limit": "684 B"
"limit": "712 B"
}
],
"clean-publish": {
Expand Down
Loading

0 comments on commit 5ae0315

Please sign in to comment.