# Router

A hash based Router

[![npm version](https://img.shields.io/npm/v/@jlbyte/router?logo=npm&label=npm)](https://www.npmjs.com/package/@jlbyte/router)
[![CI](https://github.com/jlongyam/router/actions/workflows/ci.yml/badge.svg)](https://github.com/jlongyam/router/actions/workflows/ci.yml)
[![npmx](https://img.shields.io/badge/npmx-view-blue)](https://npmx.dev/package/@jlbyte/router)
[![](https://data.jsdelivr.com/v1/package/npm/@jlbyte/router/badge)](https://www.jsdelivr.com/package/npm/@jlbyte/router)

## But why?

The the difference of this router with other hash router
is have ability to **parse sub slash after query**,

example

> `#page/test?asset=/path/to/file.js`

That slash after querty parsed properly.

## Features

- simple -> focuc to hash only
- small -> under 100 lines of code
- query friedly -> explained above
- typed -> ships its own type definitions, no `@types/*` needed
- ready to use -> `import router from '@jlbyte/router'`, no `new Router()`
- `router.match` -> work in node and browser
- `router.route` -> work in browser

## Usage

<details name="detail">

<summary>Design site map</summary>

Import

```js
import router from '@jlbyte/router'
```

1. **Basic** design

```js
console.dir( router.match('#index', ':page') );
```

```js
{ path: 'index', params: { page: 'index' }, query: {} }
```

2. **Complex** design

```js
console.dir( router.match('#page/test?asset=path/to/file.js', 'page/:any') );
```

```js
{
  path: 'page/test',
  params: { '$0': 'test' },
  query: { asset: 'path/to/file.js' }
}
```

See usage detail in **usage/design.mjs**

</details>
<details name="detail">

<summary>Test design</summary>

Example:

```js

let hash = '#lib/test/src?path=./root/file.css'
let pattern = 'lib/:n/src' 
 

let result = { 
  path: 'lib/test/src',
  params: { n: 'test' },
  query: { path: './root/file.css' }
}

console.assert( router.match( hash, pattern ), result )
```

</details>
<details name="detail">

<summary>Create route</summary>

Design:

```js
console.dir( router.match('#style/load?file=style.css', 'style/:action') )
```

Result:

```js
{
  path: 'style/load',
  params: { action: 'load' },
  query: { file: 'style.css' }
}
```

Implementation:

```js 
router.route('style/:action', ({ params, query })=> {
  if(params.action === 'load') {
    console.log( query.file )
  }
})
```

</details>
<details name="detail">
<summary>Manage Navigation</summary>

Example:

```js
router.navigate('#style/load?file=style.css')
```

See usage detail in **usage/route.mjs**

</details>
<details name="detail"><summary>Pretty URL</summary>

How to create pretty URL like `https://example.com/#/other/page`

1. Design first

```js
console.log( 'Design', router.match('#/other', '/:page') );
```

2. Catch route

```js
router.route('/:page', ({params})=> {
  console.log('Routes: ', params);
}
```

3. Test

```js
router.navigate('#/other');
```

See `usage/pretty.html` for example

</details>

## Format

<details name="detail"><summary>TypeScript</summary>

Type definitions are bundled, so editors and `tsc` pick them up without
installing anything extra:

```ts
import router from '@jlbyte/router'
import type { MatchResult, RouteMatch } from '@jlbyte/router'

const parsed: MatchResult | null = router.match('#page/test', 'page/:any')
```

</details>
<details name="detail"><summary>Javascript</summary>

Two entry points:

- `import` -> `src/router.mjs`, native **ESM** shipped as source (no build). Its
  single default export is the ready-made `router`.
- `<script>` -> `dist/router.js`, an **IIFE** that registers `window.router`
  automatically (the `Router` class is not exposed globally).

```html
<script src="https://cdn.jsdelivr.net/npm/@jlbyte/router/dist/router.js"></script>
<script>
  router.route(':page', ({ params }) => console.log(params.page))
  router.navigate('#home')
</script>
```

No wiring is needed with the script tag: `router` is already global.

</details>
<details name="detail"><summary>Legacy</summary>

`dist/router.legacy.js` is the same router transpiled to ES5, for engines that
predate ES2015.

It is **standalone**, the few runtime APIs it needs

- `Map`,
- `Symbol.iterator`,
- `Object.entries`,
- `Array.from` and
- `String.prototype.startsWith`

are shipped as small guarded shims inside the file,
so **no polyfill file has to be loaded first**.

```html
<script src="https://cdn.jsdelivr.net/npm/@jlbyte/router/dist/router.legacy.js"></script>
<script>
  router.route(':page', function (result) {
    console.log(result.params.page)
  })
</script>
```

If your page already provides those APIs, the shims detect them and do nothing.
Only the *syntax* is ES5; write the code that uses the router in ES5 too when
targeting those browsers.

`dist/` is generated by `pnpm build` (rollup) and is not committed.

</details>

## Development

- Node **20 is the minimum** (`engines.node: ">=20"`)
- Development runs on Node 26 (`.nvmrc`)
- CI checks both: 20 for the floor, 26 for the current runtime

```bash
nvm use 
pnpm install
pnpm test
pnpm typecheck
pnpm build
pnpm test:webkit
```

- To exercise the 32-bit / legacy path locally, switch down: `nvm use 20.20.0`

- NW.js is a devDependency for manual desktop checks (`pnpm usage:nw`)

```bash
pnpm approve-builds
pnpm usage:nw
```

- CI installs with `--ignore-scripts`, so that download never runs on GitHub.

## Similiar projects

Similiar project that using **hash** router

- [eRouter](https://github.com/frentsel/eRouter)
- [hyperapp-routes](https://github.com/jrop/hyperapp-routes)
- [spa-hashrouter](https://github.com/liberstack/spa-hashrouter)
- [director](https://github.com/flatiron/director)

## Acknowledgments

The resources you find helpful and would like to give credit to

- [BrowserStack](https://www.browserstack.com/open-source)
- [DeepSeek](https://www.deepseek.com/harness/)

## Roadmap

- [X] create legacy compability script
- [X] usage for pretty URL
- [ ] create custom HTML element `<ux-router>`

## License

Distributed under the [MIT](LICENSE) License.
