Repository: riot/route
Branch: main
Commit: ea8f58a95a40
Files: 39
Total size: 43.9 KB
Directory structure:
gitextract_m3uzibvz/
├── .github/
│ └── workflows/
│ └── test.yml
├── .gitignore
├── .prettierrc.js
├── .qlty/
│ └── qlty.toml
├── LICENSE
├── README.md
├── demos/
│ ├── components/
│ │ ├── app.riot
│ │ └── user.riot
│ ├── riot-history.html
│ ├── standalone-hash.html
│ └── standalone-history.html
├── eslint.config.js
├── index.d.ts
├── package.json
├── rollup.config.js
├── src/
│ ├── components/
│ │ ├── route-hoc.js
│ │ ├── route-hoc.riot
│ │ ├── router-hoc.js
│ │ └── router-hoc.riot
│ ├── constants.js
│ ├── dom.js
│ ├── get-current-route.js
│ ├── set-base.js
│ └── util.js
└── test/
├── components/
│ ├── computed-routes.riot
│ ├── history-router-app.riot
│ ├── nested-updates.riot
│ ├── recursive-updates-bug-router.riot
│ ├── recursive-updates-bug148.riot
│ ├── same-route-matches.riot
│ ├── spred-props-router.riot
│ ├── static-base-path.riot
│ └── user.riot
├── components.spec.js
├── misc.spec.js
├── setup.js
├── standalone-hash-dom.spec.js
├── standalone-history-dom.spec.js
└── util.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/test.yml
================================================
name: test
on:
push:
branches: [main, dev]
pull_request:
branches: [dev]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x, 24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Local Unit Test ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm i
- run: npm test
- name: Generate Coverage
if: ${{ success() && github.event_name != 'pull_request' && matrix.node-version == '22.x' }}
run: npm run cov
- name: Code Quality
if: ${{ success() && github.event_name != 'pull_request' && matrix.node-version == '22.x' }}
uses: qltysh/qlty-action/coverage@v1
with:
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
files: coverage/lcov.info
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# editor files
.idea
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# mac useless file
.DS_Store
# Generated files
index.js
index.umd.js
index.standalone.js
index.standalone.umd.js
demos/components/*.js
================================================
FILE: .prettierrc.js
================================================
import config from '@riotjs/prettier-config'
export default config
================================================
FILE: .qlty/qlty.toml
================================================
exclude_patterns = ["test/**", "demos/**"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) Gianluca Guarini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Riot Router
[](https://github.com/riot/route/)
[![Build Status][ci-image]][ci-url] [![Code Quality][qlty-image]][qlty-url] [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![MIT License][license-image]][license-url] [![Coverage Status][coverage-image]][coverage-url]
> Simple isomorphic router
The Riot.js Router is the minimal router implementation with such technologies:
- compatible with the DOM pushState and history API
- isomorphic functional API
- [erre.js streams](https://github.com/GianlucaGuarini/erre) and javascript async generators
- [rawth.js](https://github.com/GianlucaGuarini/rawth) urls parsing
It doesn't need Riot.js to work and can be used as standalone module.
**For Riot.js 3 and the older route version please check the [v3 branch](https://github.com/riot/route/tree/v3)**
## Table of Contents
- [Install](#install)
- [Documentation](#documentation)
- [Demos](https://github.com/riot/examples)
## Install
We have 2 editions:
| edition | file |
| :------------------------ | :------------------------ |
| **ESM Module** | `index.js` |
| **UMD Version** | `index.umd.js` |
| **Standalone ESM Module** | `index.standalone.js` |
| **Standalone UMD Module** | `index.standalone.umd.js` |
### Script injection
```html
<script src="https://unpkg.com/@riotjs/route@x.x.x/index.umd.js"></script>
```
_Note_: change the part `x.x.x` to the version numbers what you want to use: ex. `4.5.0` or `4.7.0`.
### ESM module
```js
import { route } from 'https://unpkg.com/@riotjs/route/index.js'
```
### npm
```bash
npm i -S @riotjs/route
```
### Download by yourself
- [Standalone](https://unpkg.com/@riotjs/route/route.js)
- [ESM](https://unpkg.com/@riotjs/route/route.esm.js)
## Documentation
### With Riot.js
You can import the `<router>` and `<route>` components in your application and use them as it follows:
```html
<app>
<router>
<!-- These links will trigger automatically HTML5 history events -->
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/team/gianluca">Gianluca</a>
</nav>
<!-- Your application routes will be rendered here -->
<route path="/home"> Home page </route>
<route path="/about"> About </route>
<route path="/team/:person"> Hello dear { route.params.person } </route>
</router>
<script>
import { Router, Route } from '@riotjs/route'
export default {
components { Router, Route }
}
</script>
</app>
```
You can also use the `riot.register` method to register them globally
```js
import { Route, Router } from '@riotjs/route'
import { register } from 'riot'
// now the Router and Route components are globally available
register('router', Router)
register('route', Route)
```
#### Router
The `<router>` component should wrap your application markup and will detect automatically all the clicks on links that should trigger a route event.
```html
<router>
<!-- this link will trigger a riot router event -->
<a href="/path/somewhere">Link</a>
</router>
<!-- this link will work as normal link without triggering router events -->
<a href="/path/to/a/page">Link</a>
```
You can also specify the base of your application via component attributes:
```html
<router base="/internal/path">
<!-- this link is outside the base so it will work as a normal link -->
<a href="/somewhere">Link<a>
</router>
```
The router component has also an `onStarted` callback that will be called asynchronously after the first route event will be called
```html
<router onStarted="{onRouterStarted}"></router>
```
#### Route
The `<route>` component provides the `route` property to its children (it's simply a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object) allowing you to detect the url params and queries.
```html
<route path="/:some/:route/:param"> {JSON.stringify(route.params)} </route>
<route path="/search(.*)">
<!-- Assuming the URL is "/search?q=awesome" -->
{route.searchParams.get('q')}
</route>
```
Each `<route>` component has its own lifecycle attributes in order to let you know when it gets mounted or unmounted.
```riot
<app>
<router>
<route path="/home"
on-before-mount={onBeforeHomeMount}
on-mounted={onHomeMounted}
on-before-update={onBeforeHomeUpdate}
on-updated={onHomeUpdated}
on-before-unmount={onBeforeHomeUnmount}
on-unmounted={onHomeUnmounted}
/>
</router>
</app>
```
### Standalone
This module was not only designed to be used with Riot.js but also as standalone module.
Without importing the Riot.js components in your application you can use the core methods exported to build and customize your own router compatible with any kind of frontend setup.
Depending on your project setup you might import it as follows:
```js
// in a Riot.js application
import { route } from '@riotjs/route'
// in a standalone context
import { route } from '@riotjs/route/standalone'
```
#### Fundamentals
This module works on node and on any modern browser, it exports the `router` and `router` property exposed by [rawth](https://github.com/GianlucaGuarini/rawth)
```js
import { route, router, setBase } from '@riotjs/route'
// required to set base first
setBase('/')
// create a route stream
const aboutStream = route('/about')
aboutStream.on.value((url) => {
console.log(url) // URL object
})
aboutStream.on.value(() => {
console.log('just log that the about route was triggered')
})
// triggered on each route event
router.on.value((path) => {
// path is always a string in this function
console.log(path)
})
// trigger a route change manually
router.push('/about')
// end the stream
aboutStream.end()
```
#### Base path
Before using the router in your browser you will need to set your application base path.
This setting can be configured simply via `setBase` method:
```js
import { setBase } from '@riotjs/route'
// in case you want to use the HTML5 history navigation
setBase(`/`)
// in case you use the hash navigation
setBase(`#`)
```
Setting the base path of your application route is mandatory and is the first you probably are going to do before creating your route listeners.
#### DOM binding
The example above is not really practical in case you are working in a browser environment. In that case you might want to bind your router to the DOM listening all the click events that might trigger a route change event.
Window history `popstate` events should be also connected to the router.
With the `initDomListeners` method you can automatically achieve all the features above:
```js
import { initDomListeners } from '@riotjs/route'
const unsubscribe = initDomListeners()
// the router is connected to the page DOM
// ...tear down and disconnect the router from the DOM
unsubscribe()
```
The `initDomListeners` will intercept any link click on your application. However it can also receive a HTMLElement or a list of HTMLElements as argument to scope the click listener only to a specific DOM region of your application
```js
import { initDomListeners } from '@riotjs/route'
initDomListeners(document.querySelector('.main-navigation'))
```
[ci-image]: https://img.shields.io/github/actions/workflow/status/riot/route/test.yml?style=flat-square
[ci-url]: https://github.com/riot/route/actions
[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
[license-url]: LICENSE.txt
[npm-version-image]: http://img.shields.io/npm/v/@riotjs/route.svg?style=flat-square
[npm-downloads-image]: http://img.shields.io/npm/dm/@riotjs/route.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@riotjs/route
[coverage-image]: https://qlty.sh/gh/riot/projects/route/coverage.svg
[coverage-url]: https://qlty.sh/gh/riot/projects/route
[qlty-image]: https://qlty.sh/gh/riot/projects/route/maintainability.svg
[qlty-url]: https://qlty.sh/gh/riot/projects/route
================================================
FILE: demos/components/app.riot
================================================
<app>
<router>
<nav>
<a href="/hello">Hello</a>
<a href="/user">User</a>
<a href="/user/gianluca">Username</a>
<a href="/user/gianluca/#anchor">Username with anchor</a>
<a href="/goodbye">goodbye</a>
</nav>
<route path="(.*)">
Every route :)
</route>
<route path="/hello">hello</route>
<route path="/user">user</route>
<route path="/user/:username/([?#].*)?">
<user username={route.params.username}></user>
</route>
<route path="/goodbye">goodbye</route>
</router>
<style>
nav {
display: flex;
gap: 0.6rem;
align-items: center;
}
</style>
</app>
================================================
FILE: demos/components/user.riot
================================================
<user>
User {JSON.stringify(props)}
<p id="anchor">i am an anchor</p>
<style>
p {
margin: 100vh 0;
}
</style>
</user>
================================================
FILE: demos/riot-history.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Riot.js history</title>
<script src="https://unpkg.com/riot/riot.js"></script>
<script src="../index.umd.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module">
import App from './components/app.js'
import User from './components/user.js'
riot.register('route', route.Route)
riot.register('router', route.Router)
riot.register('user', User)
riot.component(App)(app)
</script>
</body>
</html>
================================================
FILE: demos/standalone-hash.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Standalone Hash Demo</title>
<script src="../index.standalone.umd.js"></script>
</head>
<body>
<nav>
<a href="#/hello">Hello</a>
<a href="#/user">User</a>
<a href="#/user/gianluca">Username</a>
<a href="#/goodbye">goodbye</a>
</nav>
<div id="root"></div>
<script type="module">
const { initDomListeners, setBase, router } = route
setBase('#')
const onRoute = (url) =>
(root.innerHTML = `${url} and params=${JSON.stringify(url.params)}`)
route.route('/hello').on.value(onRoute)
route.route('/user').on.value(onRoute)
route.route('/user/:username').on.value(onRoute)
route.route('/goodbye').on.value(onRoute)
router.push(window.location.hash.replace('#', ''))
initDomListeners()
</script>
</body>
</html>
================================================
FILE: demos/standalone-history.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Standalone History Demo</title>
<script src="../index.standalone.umd.js"></script>
</head>
<body>
<nav>
<a href="/hello">Hello</a>
<a href="/user">User</a>
<a href="/user/gianluca">Username</a>
<a href="/goodbye">goodbye</a>
</nav>
<div id="root"></div>
<script type="module">
const { initDomListeners, setBase, router } = route
setBase('/')
const onRoute = (url) =>
(root.innerHTML = `${url} and params=${JSON.stringify(url.params)}`)
route.route('/hello').on.value(onRoute)
route.route('/user').on.value(onRoute)
route.route('/user/:username').on.value(onRoute)
route.route('/goodbye').on.value(onRoute)
initDomListeners()
</script>
</body>
</html>
================================================
FILE: eslint.config.js
================================================
import { defineConfig } from 'eslint/config'
import riotEslintConfig from 'eslint-config-riot'
export default defineConfig([
{ extends: [riotEslintConfig] },
{
rules: {
'fp/no-rest-parameters': 0,
'fp/no-mutating-methods': 0,
},
},
])
================================================
FILE: index.d.ts
================================================
import { RiotComponentWrapper, RiotComponent } from 'riot'
import { URLWithParams } from 'rawth'
export * from 'rawth'
export declare const Route: RiotComponentWrapper<
RiotComponent<{
path: string
'on-before-mount'?: (path: URLWithParams) => void
'on-mounted'?: (path: URLWithParams) => void
'on-before-unmount'?: (path: URLWithParams) => void
'on-unmounted'?: (path: URLWithParams) => void
}>
>
export declare const Router: RiotComponentWrapper<
RiotComponent<{
base?: string
'initial-route'?: string
'on-started'?: (route: string) => void
}>
>
export declare function getCurrentRoute(): string
export declare function initDomListeners(): void
export declare function setBase(base: string): void
================================================
FILE: package.json
================================================
{
"name": "@riotjs/route",
"version": "10.0.0",
"description": "Riot.js isomorphic router",
"type": "module",
"main": "index.umd.js",
"jsnext:main": "index.js",
"module": "index.js",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js",
"require": "./index.umd.js",
"browser": "./index.umd.js"
},
"./standalone": {
"types": "./index.d.ts",
"import": "./index.standalone.js",
"require": "./index.standalone.umd.js",
"browser": "./index.standalone.umd.js"
}
},
"scripts": {
"prepublishOnly": "npm run build && npm test",
"lint": "eslint src test rollup.config.js && prettier -c .",
"build": "rollup -c && npm run build-demo",
"build-demo": "riot demos/components -o demos/components",
"demo": "npm run build && serve",
"cov": "c8 report --reporter=lcov",
"cov-html": "c8 report --reporter=html",
"test": "npm run lint && c8 mocha -r test/setup.js test/*.spec.js"
},
"files": [
"index.d.ts",
"index.js",
"index.umd.js",
"index.standalone.js",
"index.standalone.umd.js"
],
"repository": {
"type": "git",
"url": "git+https://github.com/riot/route.git"
},
"keywords": [
"riot",
"Riot.js",
"router",
"riot-route",
"route"
],
"author": "Gianluca Guarini <gianluca.guarini@gmail.com> (https://gianlucaguarini.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/riot/route/issues"
},
"homepage": "https://github.com/riot/route#readme",
"devDependencies": {
"@riotjs/cli": "10.0.0",
"@riotjs/compiler": "10.0.0",
"@riotjs/prettier-config": "^1.1.0",
"@riotjs/register": "10.0.0",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-virtual": "^3.0.2",
"c8": "^10.1.3",
"chai": "^6.0.1",
"eslint": "^9.33.0",
"eslint-config-riot": "^5.0.2",
"globals": "^16.3.0",
"jsdom": "26.1.0",
"jsdom-global": "3.0.2",
"mocha": "^11.7.1",
"prettier": "^3.6.2",
"riot": "^10.0.0",
"rollup": "^4.47.1",
"rollup-plugin-riot": "10.0.0",
"serve": "^14.2.4",
"sinon": "^21.0.0",
"sinon-chai": "^4.0.1"
},
"peerDependency": {
"riot": "^6.0.0 || ^7.0.0 || ^9.0.0 || ^10.0.0"
},
"dependencies": {
"@riotjs/util": "^10.0.0",
"bianco.attr": "^1.1.1",
"bianco.events": "^1.1.1",
"bianco.query": "^1.1.4",
"cumpa": "^2.0.1",
"rawth": "^3.0.0"
}
}
================================================
FILE: rollup.config.js
================================================
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import riot from 'rollup-plugin-riot'
import { resolve as nodeResolve } from 'node:path'
import virtual from '@rollup/plugin-virtual'
const standaloneExternal = [
nodeResolve('./src/components/route-hoc.riot'),
nodeResolve('./src/components/router-hoc.riot'),
]
const defaultOptions = {
input: 'src/index.js',
plugins: [resolve(), commonjs(), riot()],
external: ['riot'],
}
const standalonePlugins = [
virtual(
standaloneExternal.reduce(
(acc, path) => ({ ...acc, [path]: 'export default {}' }),
{},
),
),
...defaultOptions.plugins,
]
export default [
{
...defaultOptions,
output: {
format: 'esm',
file: 'index.js',
},
},
{
...defaultOptions,
output: {
format: 'umd',
name: 'route',
file: 'index.umd.js',
},
},
{
...defaultOptions,
plugins: standalonePlugins,
output: {
format: 'esm',
file: 'index.standalone.js',
},
},
{
...defaultOptions,
plugins: standalonePlugins,
output: {
format: 'umd',
name: 'route',
file: 'index.standalone.umd.js',
},
},
]
================================================
FILE: src/components/route-hoc.js
================================================
import { PATH_ATTRIBUTE } from '../constants.js'
import {
route,
toRegexp,
match,
router,
createURLStreamPipe,
} from '../index.js'
import $ from 'bianco.query'
import getCurrentRoute from '../get-current-route.js'
import { get as getAttr } from 'bianco.attr'
import {
createDefaultSlot,
getAttribute,
isValidQuerySelectorString,
} from '../util.js'
import compose from 'cumpa'
const getInitialRouteValue = (pathToRegexp, path, options) => {
const route = compose(
...createURLStreamPipe(pathToRegexp, options).reverse(),
)(path)
return route.params ? route : null
}
const clearDOMBetweenNodes = (first, last, includeBoundaries) => {
const clear = (node) => {
if (!node || (node === last && !includeBoundaries)) return
const { nextSibling } = node
node.remove()
clear(nextSibling)
}
clear(includeBoundaries ? first : first.nextSibling)
}
export const routeHoc = ({ slots, attributes }) => {
const placeholders = {
before: document.createTextNode(''),
after: document.createTextNode(''),
}
return {
mount(el, context) {
// create the component state
const currentRoute = getCurrentRoute()
const path =
getAttribute(attributes, PATH_ATTRIBUTE, context)?.evaluate(context) ||
getAttr(el, PATH_ATTRIBUTE)
const pathToRegexp = toRegexp(path, [])
const state = {
pathToRegexp,
route:
currentRoute && match(currentRoute, pathToRegexp)
? getInitialRouteValue(pathToRegexp, currentRoute, {})
: null,
}
this.el = el
this.slot = createDefaultSlot([
{
isBoolean: false,
name: 'route',
evaluate: () => this.state.route,
},
])
this.context = context
this.state = state
// set the route listeners
this.boundOnBeforeRoute = this.onBeforeRoute.bind(this)
this.boundOnRoute = this.onRoute.bind(this)
router.on.value(this.boundOnBeforeRoute)
this.stream = route(path).on.value(this.boundOnRoute)
// update the DOM
el.replaceWith(placeholders.before)
placeholders.before.parentNode.insertBefore(
placeholders.after,
placeholders.before.nextSibling,
)
if (state.route) this.mountSlot()
},
update(context) {
this.context = context
if (this.state.route) this.slot.update({}, context)
},
mountSlot() {
const { route } = this.state
// insert the route root element after the before placeholder
placeholders.before.parentNode.insertBefore(
this.el,
placeholders.before.nextSibling,
)
this.callLifecycleProperty('onBeforeMount', route)
this.slot.mount(
this.el,
{
slots,
},
this.context,
)
this.callLifecycleProperty('onMounted', route)
},
clearDOM(includeBoundaries) {
// remove all the DOM nodes between the placeholders
clearDOMBetweenNodes(
placeholders.before,
placeholders.after,
includeBoundaries,
)
},
unmount() {
router.off.value(this.boundOnBeforeRoute)
this.slot.unmount({}, this.context, true)
this.clearDOM(true)
this.stream.end()
},
onBeforeRoute(path) {
const { route } = this.state
// this component was not mounted or the current path matches
// we don't need to unmount this component
if (!route || match(path, this.state.pathToRegexp)) return
this.callLifecycleProperty('onBeforeUnmount', route)
this.slot.unmount({}, this.context, true)
this.clearDOM(false)
this.state.route = null
this.callLifecycleProperty('onUnmounted', route)
},
onRoute(route) {
const prevRoute = this.state.route
this.state.route = route
// if this route component was already mounted we need to update it
if (prevRoute) {
this.callLifecycleProperty('onBeforeUpdate', route)
this.slot.update({}, this.context)
this.callLifecycleProperty('onUpdated', route)
}
// this route component was never mounted, so we need to create its DOM
else this.mountSlot()
// emulate the default browser anchor links behaviour
if (route.hash && isValidQuerySelectorString(route.hash))
$(route.hash)?.[0].scrollIntoView()
},
callLifecycleProperty(method, ...params) {
const attr = getAttribute(attributes, method, this.context)
if (attr) attr.evaluate(this.context)(...params)
},
}
}
================================================
FILE: src/components/route-hoc.riot
================================================
<route-hoc>
<script>
import { pure } from 'riot'
import { routeHoc } from './route-hoc.js'
export default pure(routeHoc)
</script>
</route-hoc>
================================================
FILE: src/components/router-hoc.js
================================================
import { router } from '../index.js'
import { defer, cancelDefer, getAttribute, createDefaultSlot } from '../util.js'
import getCurrentRoute from '../get-current-route.js'
import setBase from '../set-base.js'
import { panic } from '@riotjs/util/misc'
import initDomListeners from '../dom.js'
const BASE_ATTRIBUTE_NAME = 'base'
const INITIAL_ROUTE = 'initialRoute'
const ON_STARTED_ATTRIBUTE_NAME = 'onStarted'
export const routerHoc = ({ slots, attributes, props }) => {
if (routerHoc.wasInitialized)
panic('Multiple <router> components are not supported')
return {
slot: null,
el: null,
teardown: null,
mount(el, context) {
const initialRouteAttr = getAttribute(attributes, INITIAL_ROUTE, context)
const initialRoute = initialRouteAttr
? initialRouteAttr.evaluate(context)
: null
const currentRoute = getCurrentRoute()
const onFirstRoute = () => {
this.createSlot(context)
router.off.value(onFirstRoute)
}
routerHoc.wasInitialized = true
this.el = el
this.teardown = initDomListeners(this.root)
this.setBase(context)
// mount the slots only if the current route was defined
if (currentRoute && !initialRoute) {
this.createSlot(context)
} else {
router.on.value(onFirstRoute)
router.push(initialRoute || window.location.href)
}
},
createSlot(context) {
if (!slots || !slots.length) return
const onStartedAttr = getAttribute(
attributes,
ON_STARTED_ATTRIBUTE_NAME,
context,
)
this.slot = createDefaultSlot()
this.slot.mount(
this.el,
{
slots,
},
context,
)
if (onStartedAttr) {
onStartedAttr.evaluate(context)(getCurrentRoute())
}
},
update(context) {
this.setBase(context)
// defer the updates to avoid internal recursive update calls
// see https://github.com/riot/route/issues/148
if (this.slot) {
cancelDefer(this.deferred)
this.deferred = defer(() => {
this.slot.update({}, context)
})
}
},
unmount(...args) {
this.teardown()
routerHoc.wasInitialized = false
if (this.slot) {
this.slot.unmount(...args)
}
},
getBase(context) {
const baseAttr = getAttribute(attributes, BASE_ATTRIBUTE_NAME, context)
return baseAttr
? baseAttr.evaluate(context)
: this.el.getAttribute(BASE_ATTRIBUTE_NAME) || '/'
},
setBase(context) {
setBase(props ? props.base : this.getBase(context))
},
}
}
// flag to avoid multiple router instances
routerHoc.wasInitialized = false
================================================
FILE: src/components/router-hoc.riot
================================================
<router-hoc>
<script>
import { pure } from 'riot'
import { routerHoc } from './router-hoc.js'
export default pure(routerHoc)
</script>
</router-hoc>
================================================
FILE: src/constants.js
================================================
export const WINDOW_EVENTS = 'popstate'
export const CLICK_EVENT = 'click'
export const DOWNLOAD_LINK_ATTRIBUTE = 'download'
export const HREF_LINK_ATTRIBUTE = 'href'
export const TARGET_SELF_LINK_ATTRIBUTE = '_self'
export const LINK_TAG_NAME = 'A'
export const HASH = '#'
export const SLASH = '/'
export const PATH_ATTRIBUTE = 'path'
export const RE_ORIGIN = /^.+?\/\/+[^/]+/
================================================
FILE: src/dom.js
================================================
import {
CLICK_EVENT,
DOWNLOAD_LINK_ATTRIBUTE,
HREF_LINK_ATTRIBUTE,
LINK_TAG_NAME,
RE_ORIGIN,
TARGET_SELF_LINK_ATTRIBUTE,
WINDOW_EVENTS,
} from './constants.js'
import { add, remove } from 'bianco.events'
import { defaults, router } from 'rawth'
import { getDocument, getHistory, getLocation, getWindow } from './util.js'
import { has } from 'bianco.attr'
const onWindowEvent = () =>
router.push(normalizePath(String(getLocation().href)))
const onRouterPush = (path) => {
const url = path.includes(defaults.base) ? path : defaults.base + path
const loc = getLocation()
const hist = getHistory()
const doc = getDocument()
// update the browser history only if it's necessary
if (hist && url !== loc.href) {
hist.pushState(null, doc.title, url)
}
}
const getLinkElement = (node) =>
node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node
const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME
const isCrossOriginLink = (path) =>
path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1
const isTargetSelfLink = (el) =>
el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE
const isEventForbidden = (event) =>
(event.which && event.which !== 1) || // not left click
event.metaKey ||
event.ctrlKey ||
event.shiftKey || // or meta keys
event.defaultPrevented // or default prevented
const isForbiddenLink = (el) =>
!el ||
!isLinkNode(el) || // not A tag
has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr
!has(el, HREF_LINK_ATTRIBUTE) || // has no href attr
isTargetSelfLink(el) ||
isCrossOriginLink(el.href)
const normalizePath = (path) => path.replace(defaults.base, '')
const isInBase = (path) => !defaults.base || path.includes(defaults.base)
/**
* Callback called anytime something will be clicked on the page
* @param {Event} event - click event
* @returns {undefined} void method
*/
const onClick = (event) => {
if (isEventForbidden(event)) return
const el = getLinkElement(event.target)
if (isForbiddenLink(el) || !isInBase(el.href)) return
event.preventDefault()
router.push(normalizePath(el.href))
}
/**
* Link the rawth router to the DOM events
* @param { HTMLElement } container - DOM node where the links are located
* @returns {Function} teardown function
*/
export default function initDomListeners(container) {
const win = getWindow()
const root = container || getDocument()
if (win) {
add(win, WINDOW_EVENTS, onWindowEvent)
add(root, CLICK_EVENT, onClick)
}
router.on.value(onRouterPush)
return () => {
if (win) {
remove(win, WINDOW_EVENTS, onWindowEvent)
remove(root, CLICK_EVENT, onClick)
}
router.off.value(onRouterPush)
}
}
================================================
FILE: src/get-current-route.js
================================================
import { router } from 'rawth'
const getCurrentRoute = ((currentRoute) => {
// listen the route changes events to store the current route
router.on.value((r) => (currentRoute = r))
return () => {
return currentRoute
}
})(null)
export default getCurrentRoute
================================================
FILE: src/set-base.js
================================================
import { HASH, SLASH } from './constants.js'
import { configure } from 'rawth'
import { getWindow } from './util.js'
export const normalizeInitialSlash = (str) =>
str[0] === SLASH ? str : `${SLASH}${str}`
export const removeTrailingSlash = (str) =>
str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str
export const normalizeBase = (base) => {
const win = getWindow()
const loc = win.location
const root = loc ? `${loc.protocol}//${loc.host}` : ''
const { pathname } = loc ? loc : {}
switch (true) {
// pure root url + pathname
case Boolean(base) === false:
return removeTrailingSlash(`${root}${pathname || ''}`)
// full path base
case /(www|http(s)?:)/.test(base):
return base
// hash navigation
case base[0] === HASH:
return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}`
// root url with trailing slash
case base === SLASH:
return removeTrailingSlash(root)
// custom pathname
default:
return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`)
}
}
export default function setBase(base) {
configure({ base: normalizeBase(base) })
}
================================================
FILE: src/util.js
================================================
import { dashToCamelCase } from '@riotjs/util/strings'
import { isNil } from '@riotjs/util/checks'
import { __ } from 'riot'
export const getGlobal = () => getWindow() || global
export const getWindow = () => (typeof window === 'undefined' ? null : window)
export const getDocument = () =>
typeof document === 'undefined' ? null : document
export const getHistory = () =>
typeof history === 'undefined' ? null : history
export const getLocation = () => {
const win = getWindow()
return win ? win.location : {}
}
export const defer = (() => {
const globalScope = getGlobal()
return globalScope.requestAnimationFrame || globalScope.setTimeout
})()
export const cancelDefer = (() => {
const globalScope = getGlobal()
return globalScope.cancelAnimationFrame || globalScope.clearTimeout
})()
export const getAttribute = (attributes, name, context) => {
if (!attributes) return null
const normalizedAttributes = attributes.flatMap((attr) =>
isNil(attr.name)
? // add support for spread attributes https://github.com/riot/route/issues/178
Object.entries(attr.evaluate(context)).map(([key, value]) => ({
// evaluate each value of the spread attribute and store it into the array
name: key,
// create a nested evaluate function pointing to the original value of the spread object
evaluate: () => value,
}))
: attr,
)
return normalizedAttributes.find((a) => dashToCamelCase(a.name) === name)
}
export const createDefaultSlot = (attributes = []) => {
const { template, bindingTypes, expressionTypes } = __.DOMBindings
return template(null, [
{
type: bindingTypes.SLOT,
name: 'default',
attributes: attributes.map((attr) => ({
...attr,
type: expressionTypes.ATTRIBUTE,
})),
},
])
}
// True if the selector string is valid
export const isValidQuerySelectorString = (selector) =>
/^([a-zA-Z0-9-_*#.:[\]\s>+~()='"]|\\.)+$/.test(selector)
================================================
FILE: test/components/computed-routes.riot
================================================
<computed-routes>
<router>
<route path={state.home}>
<p>{state.name}</p>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
export default {
components: {
Router,
Route,
},
state: {
home: '/home',
name: 'hello'
},
}
</script>
</computed-routes>
================================================
FILE: test/components/history-router-app.riot
================================================
<history-router-app>
<h1>{state.message}</h1>
<router base={props.base} on-started={onStarted}>
<route path="/">
<p>Hello</p>
</route>
<route path="/goodbye/:user" on-mounted={() => this.update({
message: 'Title'
})}>
<user name={route.params.user}/>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
import User from './user.riot'
export default {
components: {
Router,
Route,
User,
},
state: {
message: ''
},
onStarted(currentRoute) {
this.isRouterStarted = true
this.currentRoute = currentRoute
}
}
</script>
</history-router-app>
================================================
FILE: test/components/nested-updates.riot
================================================
<nested-updates>
<router>
<user name={state.name} update-name={updateName}/>
</router>
<script>
import User from './user.riot'
import { Router, Route } from '../../src/index.js'
export default {
components: {
Router,
Route,
User
},
state: {
name: 'hello'
},
updateName() {
this.update({
name: 'goodbye'
})
}
}
</script>
</nested-updates>
================================================
FILE: test/components/recursive-updates-bug-router.riot
================================================
<recursive-updates-bug-router>
<router base={props.base}>
<route path="/">
<recursive-updates-bug148 message={state.message} callbacks={state.callbacks}>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
import RecursiveUpdatesBug148 from './recursive-updates-bug148.riot'
export default {
components: {
Router,
Route,
RecursiveUpdatesBug148
},
state: {
callbacks: [],
message: '',
},
onBeforeMount() {
this.state.callbacks.push((message) => {
this.update({
message
})
})
}
}
</script>
</recursive-updates-bug-router>
================================================
FILE: test/components/recursive-updates-bug148.riot
================================================
<recursive-updates-bug148>
<p>{props.message}</p>
<script>
export default {
onMounted(props) {
props.callbacks.forEach(fn => fn('hello'))
}
}
</script>
</recursive-updates-bug148>
================================================
FILE: test/components/same-route-matches.riot
================================================
<same-route-matches>
<router>
<route path="(.*)">
<p>{state.message}</p>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
export default {
components: {
Router,
Route,
},
state: {
message: 'hello',
}
}
</script>
</same-route-matches>
================================================
FILE: test/components/spred-props-router.riot
================================================
<spread-props-router>
<h1>{state.message}</h1>
<router {...props} on-started={onStarted}>
<route path="/">
<p>Hello</p>
</route>
<route path="/goodbye/:user" on-mounted={() => this.update({
message: 'Title'
})}>
<user name={route.params.user}/>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
import User from './user.riot'
export default {
components: {
Router,
Route,
User,
},
state: {
message: ''
},
onStarted(currentRoute) {
this.isRouterStarted = true
this.currentRoute = currentRoute
}
}
</script>
</spread-props-router>
================================================
FILE: test/components/static-base-path.riot
================================================
<static-base-path>
<router base="/app">
<a href="/home">
Home
</a>
<route path="(.*)">
<p>{state.message}</p>
</route>
</router>
<script>
import { Router, Route } from '../../src/index.js'
export default {
components: {
Router,
Route,
},
state: {
message: 'hello',
}
}
</script>
</static-base-path>
================================================
FILE: test/components/user.riot
================================================
<user>
<p>{props.name}</p>
<script>
export default {
onMounted() {
if (this.props.updateName) {
this.props.updateName()
}
}
}
</script>
</user>
================================================
FILE: test/components.spec.js
================================================
import { base, sleep } from './util.js'
import HistoryRouterApp from './components/history-router-app.riot'
import SpreadPropsRouter from './components/spred-props-router.riot'
import NestedUpdates from './components/nested-updates.riot'
import RecursiveUpdatesBugRouter from './components/recursive-updates-bug-router.riot'
import StaticBasePath from './components/static-base-path.riot'
import SameRouteMatches from './components/same-route-matches.riot'
import ComputedRoutes from './components/computed-routes.riot'
import { component } from 'riot'
import { expect } from 'chai'
import { router, defaults } from '../src/index.js'
describe('components', function () {
beforeEach(async function () {
router.push('/')
})
it('The router contents get properly rendered', async function () {
const el = document.createElement('div')
const comp = component(HistoryRouterApp)(el, {
base,
})
await sleep()
expect(comp.$('p')).to.be.ok
expect(comp.isRouterStarted).to.be.ok
expect(comp.currentRoute).to.be.ok
router.push('/goodbye/gianluca')
await sleep()
expect(comp.$('user p').innerHTML).to.be.equal('gianluca')
expect(comp.$('h1').innerHTML).to.be.equal('Title')
comp.unmount()
})
it('The Router component accepts spread props', async function () {
const el = document.createElement('div')
const comp = component(SpreadPropsRouter)(el, {
base,
})
await sleep()
expect(comp.$('p')).to.be.ok
expect(comp.isRouterStarted).to.be.ok
expect(comp.currentRoute).to.be.ok
router.push('/goodbye/gianluca')
await sleep()
expect(comp.$('user p').innerHTML).to.be.equal('gianluca')
expect(comp.$('h1').innerHTML).to.be.equal('Title')
comp.unmount()
})
it('The Route Context gets properly updated', async function () {
const el = document.createElement('div')
const comp = component(NestedUpdates)(el, {
base,
})
await sleep()
expect(comp.$('p')).to.be.ok
await sleep()
expect(comp.$('user p').innerHTML).to.be.equal('goodbye')
comp.unmount()
})
it('Recursive onMounted callbacks (bug 148) ', async function () {
const el = document.createElement('div')
const comp = component(RecursiveUpdatesBugRouter)(el, {
base,
})
await sleep()
expect(comp.$('p').innerHTML).to.be.equal('hello')
await sleep()
router.push('/')
await sleep()
expect(comp.$('p').innerHTML).to.be.equal('hello')
comp.unmount()
})
it('Static base path attributes are supported (bug 172) ', async function () {
const el = document.createElement('div')
const comp = component(StaticBasePath)(el)
expect(defaults.base).to.be.equal('https://riot.rocks/app')
comp.unmount()
})
it('Routes matched multiple times do not render twice (bug 173) ', async function () {
const el = document.createElement('div')
const comp = component(SameRouteMatches)(el)
expect(comp.$$('p')).to.have.length(1)
router.push('/foo')
await sleep()
expect(comp.$$('p')).to.have.length(1)
router.push('/')
await sleep()
expect(comp.$$('p')).to.have.length(1)
comp.unmount()
})
it('Computed routes get properly rendered', async function () {
const el = document.createElement('div')
const comp = component(ComputedRoutes)(el)
expect(comp.$('p')).to.be.not.ok
router.push('/home')
await sleep()
expect(comp.$('p')).to.be.ok
await sleep()
router.push('/')
await sleep()
expect(comp.$('p')).to.be.not.ok
comp.unmount()
})
})
================================================
FILE: test/misc.spec.js
================================================
import { base, sleep } from './util.js'
import { getCurrentRoute, router, setBase } from '../src/index.js'
import { expect } from 'chai'
import { normalizeBase } from '../src/set-base.js'
describe('misc methods', function () {
beforeEach(() => {
setBase(`${base}#`)
})
it('getCurrentRoute returns properly the current router value', async function () {
router.push(`${base}#/hello`)
await sleep()
expect(getCurrentRoute()).to.be.equal(`${base}#/hello`)
})
it('normalizeBase returns the expected paths', async function () {
expect(normalizeBase('#')).to.be.equal(`${base}#`)
expect(normalizeBase('/')).to.be.equal(`${base}`)
expect(normalizeBase('')).to.be.equal(`${base}`)
expect(normalizeBase('/hello')).to.be.equal(`${base}/hello`)
expect(normalizeBase('hello')).to.be.equal(`${base}/hello`)
expect(normalizeBase('http://google.com')).to.be.equal('http://google.com')
expect(normalizeBase('/page#anchor')).to.be.equal(`${base}/page#anchor`)
})
})
================================================
FILE: test/setup.js
================================================
import { base } from './util.js'
import { register } from 'node:module'
import { pathToFileURL } from 'node:url'
import jsdomGlobal from 'jsdom-global'
import sinonChai from 'sinon-chai'
import { use } from 'chai'
register('@riotjs/register', pathToFileURL('./'))
jsdomGlobal(null, {
url: base,
})
use(sinonChai)
================================================
FILE: test/standalone-hash-dom.spec.js
================================================
import { base, sleep } from './util.js'
import { route, router, setBase } from '../src/index.js'
import { expect } from 'chai'
import { spy } from 'sinon'
describe('standalone hash', function () {
beforeEach(() => {
setBase('#')
})
afterEach(() => {
window.history.replaceState(null, '', '/')
})
it('hash links dispatch events', async function () {
const onRoute = spy()
const hello = route('/hello').on.value(onRoute)
router.push(`${base}#/hello`)
await sleep()
expect(onRoute).to.have.been.called
hello.end()
})
it('hash links receive parameters', (done) => {
const user = route('/user/:username').on.value((url) => {
user.end()
expect(url.params).to.be.deep.equal({ username: 'gianluca' })
done()
})
router.push(`${base}#/user/gianluca`)
})
})
================================================
FILE: test/standalone-history-dom.spec.js
================================================
import { fireEvent, sleep } from './util.js'
import { initDomListeners, route, setBase } from '../src/index.js'
import $ from 'bianco.query'
import { expect } from 'chai'
import { spy } from 'sinon'
describe('standalone history', function () {
let teardown // eslint-disable-line
beforeEach(() => {
setBase('/')
document.body.innerHTML = `
<nav>
<a href="/hello">Hello</a>
<a href="/user">User</a>
<a href="/goodbye">goodbye</a>
<a href="/user/gianluca">Username</a>
<a href="/hello#anchor">Anchor</a>
</nav>
`
teardown = initDomListeners($('nav')[0])
})
afterEach(() => {
document.body.innerHTML = ''
window.history.replaceState(null, '', '/')
teardown()
})
it('html5 history links dispatch events', async function () {
const onRoute = spy()
const hello = route('/hello').on.value(onRoute)
const [a] = $('nav > a:first-of-type')
fireEvent(a, 'click')
await sleep()
expect(window.location.pathname).to.be.equal('/hello')
expect(onRoute).to.have.been.called
hello.end()
})
it('html5 history links receive parameters', (done) => {
const user = route('/user/:username').on.value((url) => {
user.end()
expect(url.params).to.be.deep.equal({ username: 'gianluca' })
done()
})
const [a] = $('nav > a:nth-child(4)')
fireEvent(a, 'click')
})
it('hash links are supported', async () => {
const onRoute = spy()
const hello = route('/hello(/?[?#].*)?').on.value(onRoute)
const [a] = $('nav > a:nth-child(5)')
fireEvent(a, 'click')
await sleep()
expect(onRoute).to.have.been.called
expect(window.location.pathname).to.be.equal('/hello')
expect(window.location.hash).to.be.equal('#anchor')
hello.end()
})
})
================================================
FILE: test/util.js
================================================
export function fireEvent(el, name) {
const e = new Event(name, { bubbles: true, cancelable: false })
el.dispatchEvent(e)
}
export const sleep = (timeout) => new Promise((r) => setTimeout(r, timeout))
export const base = 'https://riot.rocks'
gitextract_m3uzibvz/
├── .github/
│ └── workflows/
│ └── test.yml
├── .gitignore
├── .prettierrc.js
├── .qlty/
│ └── qlty.toml
├── LICENSE
├── README.md
├── demos/
│ ├── components/
│ │ ├── app.riot
│ │ └── user.riot
│ ├── riot-history.html
│ ├── standalone-hash.html
│ └── standalone-history.html
├── eslint.config.js
├── index.d.ts
├── package.json
├── rollup.config.js
├── src/
│ ├── components/
│ │ ├── route-hoc.js
│ │ ├── route-hoc.riot
│ │ ├── router-hoc.js
│ │ └── router-hoc.riot
│ ├── constants.js
│ ├── dom.js
│ ├── get-current-route.js
│ ├── set-base.js
│ └── util.js
└── test/
├── components/
│ ├── computed-routes.riot
│ ├── history-router-app.riot
│ ├── nested-updates.riot
│ ├── recursive-updates-bug-router.riot
│ ├── recursive-updates-bug148.riot
│ ├── same-route-matches.riot
│ ├── spred-props-router.riot
│ ├── static-base-path.riot
│ └── user.riot
├── components.spec.js
├── misc.spec.js
├── setup.js
├── standalone-hash-dom.spec.js
├── standalone-history-dom.spec.js
└── util.js
SYMBOL INDEX (30 symbols across 6 files)
FILE: src/components/route-hoc.js
method mount (line 45) | mount(el, context) {
method update (line 82) | update(context) {
method mountSlot (line 86) | mountSlot() {
method clearDOM (line 103) | clearDOM(includeBoundaries) {
method unmount (line 111) | unmount() {
method onBeforeRoute (line 117) | onBeforeRoute(path) {
method onRoute (line 129) | onRoute(route) {
method callLifecycleProperty (line 146) | callLifecycleProperty(method, ...params) {
FILE: src/components/router-hoc.js
constant BASE_ATTRIBUTE_NAME (line 8) | const BASE_ATTRIBUTE_NAME = 'base'
constant INITIAL_ROUTE (line 9) | const INITIAL_ROUTE = 'initialRoute'
constant ON_STARTED_ATTRIBUTE_NAME (line 10) | const ON_STARTED_ATTRIBUTE_NAME = 'onStarted'
method mount (line 20) | mount(el, context) {
method createSlot (line 45) | createSlot(context) {
method update (line 67) | update(context) {
method unmount (line 80) | unmount(...args) {
method getBase (line 88) | getBase(context) {
method setBase (line 95) | setBase(context) {
FILE: src/constants.js
constant WINDOW_EVENTS (line 1) | const WINDOW_EVENTS = 'popstate'
constant CLICK_EVENT (line 2) | const CLICK_EVENT = 'click'
constant DOWNLOAD_LINK_ATTRIBUTE (line 3) | const DOWNLOAD_LINK_ATTRIBUTE = 'download'
constant HREF_LINK_ATTRIBUTE (line 4) | const HREF_LINK_ATTRIBUTE = 'href'
constant TARGET_SELF_LINK_ATTRIBUTE (line 5) | const TARGET_SELF_LINK_ATTRIBUTE = '_self'
constant LINK_TAG_NAME (line 6) | const LINK_TAG_NAME = 'A'
constant HASH (line 7) | const HASH = '#'
constant SLASH (line 8) | const SLASH = '/'
constant PATH_ATTRIBUTE (line 9) | const PATH_ATTRIBUTE = 'path'
constant RE_ORIGIN (line 10) | const RE_ORIGIN = /^.+?\/\/+[^/]+/
FILE: src/dom.js
function initDomListeners (line 73) | function initDomListeners(container) {
FILE: src/set-base.js
function setBase (line 35) | function setBase(base) {
FILE: test/util.js
function fireEvent (line 1) | function fireEvent(el, name) {
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
{
"path": ".github/workflows/test.yml",
"chars": 991,
"preview": "name: test\n\non:\n push:\n branches: [main, dev]\n pull_request:\n branches: [dev]\n\npermissions:\n contents: read\n\njo"
},
{
"path": ".gitignore",
"chars": 643,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscov"
},
{
"path": ".prettierrc.js",
"chars": 67,
"preview": "import config from '@riotjs/prettier-config'\nexport default config\n"
},
{
"path": ".qlty/qlty.toml",
"chars": 43,
"preview": "exclude_patterns = [\"test/**\", \"demos/**\"]\n"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) Gianluca Guarini\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 8100,
"preview": "# Riot Router\n\n[](https://"
},
{
"path": "demos/components/app.riot",
"chars": 660,
"preview": "<app>\n <router>\n <nav>\n\n <a href=\"/hello\">Hello</a>\n <a href=\"/user\">User</a>\n <a href=\"/user/gianluc"
},
{
"path": "demos/components/user.riot",
"chars": 142,
"preview": "<user>\n User {JSON.stringify(props)}\n\n <p id=\"anchor\">i am an anchor</p>\n\n <style>\n p {\n margin: 100vh 0;\n "
},
{
"path": "demos/riot-history.html",
"chars": 604,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />"
},
{
"path": "demos/standalone-hash.html",
"chars": 956,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />"
},
{
"path": "demos/standalone-history.html",
"chars": 897,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />"
},
{
"path": "eslint.config.js",
"chars": 262,
"preview": "import { defineConfig } from 'eslint/config'\nimport riotEslintConfig from 'eslint-config-riot'\n\nexport default defineCon"
},
{
"path": "index.d.ts",
"chars": 742,
"preview": "import { RiotComponentWrapper, RiotComponent } from 'riot'\nimport { URLWithParams } from 'rawth'\n\nexport * from 'rawth'\n"
},
{
"path": "package.json",
"chars": 2499,
"preview": "{\n \"name\": \"@riotjs/route\",\n \"version\": \"10.0.0\",\n \"description\": \"Riot.js isomorphic router\",\n \"type\": \"module\",\n "
},
{
"path": "rollup.config.js",
"chars": 1219,
"preview": "import commonjs from '@rollup/plugin-commonjs'\nimport resolve from '@rollup/plugin-node-resolve'\nimport riot from 'rollu"
},
{
"path": "src/components/route-hoc.js",
"chars": 4557,
"preview": "import { PATH_ATTRIBUTE } from '../constants.js'\nimport {\n route,\n toRegexp,\n match,\n router,\n createURLStreamPipe,"
},
{
"path": "src/components/route-hoc.riot",
"chars": 161,
"preview": "<route-hoc>\n <script>\n import { pure } from 'riot'\n import { routeHoc } from './route-hoc.js'\n\n export default"
},
{
"path": "src/components/router-hoc.js",
"chars": 2726,
"preview": "import { router } from '../index.js'\nimport { defer, cancelDefer, getAttribute, createDefaultSlot } from '../util.js'\nim"
},
{
"path": "src/components/router-hoc.riot",
"chars": 165,
"preview": "<router-hoc>\n <script>\n import { pure } from 'riot'\n import { routerHoc } from './router-hoc.js'\n export defau"
},
{
"path": "src/constants.js",
"chars": 378,
"preview": "export const WINDOW_EVENTS = 'popstate'\nexport const CLICK_EVENT = 'click'\nexport const DOWNLOAD_LINK_ATTRIBUTE = 'downl"
},
{
"path": "src/dom.js",
"chars": 2706,
"preview": "import {\n CLICK_EVENT,\n DOWNLOAD_LINK_ATTRIBUTE,\n HREF_LINK_ATTRIBUTE,\n LINK_TAG_NAME,\n RE_ORIGIN,\n TARGET_SELF_LI"
},
{
"path": "src/get-current-route.js",
"chars": 273,
"preview": "import { router } from 'rawth'\n\nconst getCurrentRoute = ((currentRoute) => {\n // listen the route changes events to sto"
},
{
"path": "src/set-base.js",
"chars": 1168,
"preview": "import { HASH, SLASH } from './constants.js'\nimport { configure } from 'rawth'\nimport { getWindow } from './util.js'\n\nex"
},
{
"path": "src/util.js",
"chars": 1986,
"preview": "import { dashToCamelCase } from '@riotjs/util/strings'\nimport { isNil } from '@riotjs/util/checks'\nimport { __ } from 'r"
},
{
"path": "test/components/computed-routes.riot",
"chars": 364,
"preview": "<computed-routes>\n <router>\n <route path={state.home}>\n <p>{state.name}</p>\n </route>\n </router>\n <script>"
},
{
"path": "test/components/history-router-app.riot",
"chars": 713,
"preview": "<history-router-app>\n <h1>{state.message}</h1>\n <router base={props.base} on-started={onStarted}>\n <route path=\"/\">"
},
{
"path": "test/components/nested-updates.riot",
"chars": 461,
"preview": "<nested-updates>\n <router>\n <user name={state.name} update-name={updateName}/>\n </router>\n <script>\n import Use"
},
{
"path": "test/components/recursive-updates-bug-router.riot",
"chars": 711,
"preview": "<recursive-updates-bug-router>\n <router base={props.base}>\n <route path=\"/\">\n <recursive-updates-bug148 message"
},
{
"path": "test/components/recursive-updates-bug148.riot",
"chars": 213,
"preview": "<recursive-updates-bug148>\n <p>{props.message}</p>\n <script>\n export default {\n onMounted(props) {\n pro"
},
{
"path": "test/components/same-route-matches.riot",
"chars": 348,
"preview": "<same-route-matches>\n <router>\n <route path=\"(.*)\">\n <p>{state.message}</p>\n </route>\n </router>\n\n <script"
},
{
"path": "test/components/spred-props-router.riot",
"chars": 708,
"preview": "<spread-props-router>\n <h1>{state.message}</h1>\n <router {...props} on-started={onStarted}>\n <route path=\"/\">\n "
},
{
"path": "test/components/static-base-path.riot",
"chars": 399,
"preview": "<static-base-path>\n <router base=\"/app\">\n <a href=\"/home\">\n Home\n </a>\n <route path=\"(.*)\">\n <p>{s"
},
{
"path": "test/components/user.riot",
"chars": 196,
"preview": "<user>\n <p>{props.name}</p>\n\n <script>\n export default {\n onMounted() {\n if (this.props.updateName) {\n "
},
{
"path": "test/components.spec.js",
"chars": 3623,
"preview": "import { base, sleep } from './util.js'\nimport HistoryRouterApp from './components/history-router-app.riot'\nimport Sprea"
},
{
"path": "test/misc.spec.js",
"chars": 1010,
"preview": "import { base, sleep } from './util.js'\nimport { getCurrentRoute, router, setBase } from '../src/index.js'\nimport { expe"
},
{
"path": "test/setup.js",
"chars": 318,
"preview": "import { base } from './util.js'\nimport { register } from 'node:module'\nimport { pathToFileURL } from 'node:url'\nimport "
},
{
"path": "test/standalone-hash-dom.spec.js",
"chars": 833,
"preview": "import { base, sleep } from './util.js'\nimport { route, router, setBase } from '../src/index.js'\nimport { expect } from "
},
{
"path": "test/standalone-history-dom.spec.js",
"chars": 1801,
"preview": "import { fireEvent, sleep } from './util.js'\nimport { initDomListeners, route, setBase } from '../src/index.js'\nimport $"
},
{
"path": "test/util.js",
"chars": 249,
"preview": "export function fireEvent(el, name) {\n const e = new Event(name, { bubbles: true, cancelable: false })\n\n el.dispatchEv"
}
]
About this extraction
This page contains the full source code of the riot/route GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (43.9 KB), approximately 12.5k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.