Skip to content

marcodpt/wand

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

25 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Wand

A tiny JS router that makes no assumptions about its usage.

Demo License: MIT GitHub Tag bundlejs

❀️ Features

πŸ’‘ Showcase: A hash router

Wand makes no assumptions about the environment it will be used in, and this example can be easily migrated to a server-side router or any other type of application.

Demo Source

<body>
  <nav>
    <a href="#/">Home</a> |
    <a href="#/hello/Mary">Hello Mary</a> |
    <a
      href="#/hello/John?age=25&pets[]=dog&pets[]=cat"
    >Hello John</a> |
    <a href="#/goodbye?name=stranger">Goodbye</a> |
    <a href="#/clock">Clock</a> |
    <a href="#/wrong/route">404</a> |
    <a href="javascript:stop()">Stop Router</a> |
    <a href="https://github.com/marcodpt/wand">Repository</a>
  </nav>
  <main>
    <h1>Home Page</h1>
  </main>
  <pre>
    <code></code>
  </pre>
  <script type="module">
    import {hashRouter} from "https://cdn.jsdelivr.net/gh/marcodpt/wand/index.js"

    window.stop = hashRouter({
      init: () => {
        const main = document.body.querySelector('main')
        return {
          index: 0,
          home: main.innerHTML.trim(),
          render: html => {main.innerHTML = html}
        }
      },
      routes: {
        '/': ({
          render, home
        }) => render(home),
        '/hello/:name': ({
          render, Params
        }) => render(`<h1>Hello ${Params.name}</h1>`),
        '/clock': ({render}) => {
          const tick = () => {
            console.log('tick')
            const time = new Date()
            render(`<h1>${[
              time.getHours(),
              time.getMinutes(),
              time.getSeconds()
            ].map(n => (n < 10 ? '0' : '')+n).join(':')}</h1>`)
          }
          const itv = setInterval(tick, 100)
          return () => {clearInterval(itv)}
        },
        '/goodbye': ({render, Query}) => {
          render(`<h1>Goodbye message before leaving!</h1>`)
          return () => {window.alert(`Goodbye ${Query.name}!`)}
        },
        '*': ({
          render
        }) => render(`<h1>404: Page Not Found</h1>`) 
      },
      plugins: [
        state => {
          state.index++
        },
        state => {
          document.body.querySelector('code').
            textContent = JSON.stringify(state, undefined, 2)
        }
      ]
    })
  </script>
</body>

πŸ’» Usage

Hash router in the browser.

This example already makes use of the queryParser plugin and implements the runtime of a hash router.

import {hashRouter} from "https://cdn.jsdelivr.net/gh/marcodpt/wand/index.js"

Building your own runtime

This example is for those who want a more customized experience to create their own runtime and eventually make use of plugins.

import {wand, queryParser} from "https://cdn.jsdelivr.net/gh/marcodpt/wand/index.js"

πŸ“– API

runtimes: ({init?, routes, plugins?}) => stop

Runtimes are thin layers built on top of Wand with the aim of bringing simplicity to a specific use, they are open to contributions.

hashRouter

Made to be used in the browser, it already has the queryParser plugin by default.

Source

plugin: state => ()

Plugins can modify the state with each change of route, and were designed to be easy to develop without changing the way the Wand core works.

Enabling maximum flexibility and contributions.

queryParser

The default parser for query string due to its simplicity. Adds the Query property to the state.

Source

Query: object

Parsed query string.

wand: ({init?, routes, plugins?, runtime}) => stop

Runtimes are built on top of the library. And the plugins are designed for it.

The idea is to bring a simple router to your core but at the same time with a complete plugin system and easy-to-use runtimes.

Source

init: () => state

Optional function called once to create the initial state (this must be an object).

routes: {route: action}

Object that defines the possible routes.

route: string

Accepts * to match any path and :param to declare variable.

action: (state, ...args) => done | response

A function that will be called whenever route is matched in a route change.

All other args passed by the change function will be passed to the action.

done: state => ()

An optional function that will be called before the new route action, with the state of the new route to end the current route.

response: any

If it is not a done function, it will be treated as a response and returned in the change function.

plugins: [state => ()]

An optional array of plugins, which are executed sequentially with each route change and which can modify the state before the action associated with the new route or the done function associated with the old route are called.

runtime: change => finish?

The router runtime.

change: (url, ...args) => response

Whenever called, it will trigger a change of route, with the url (string) being associated with the state.

All other args will be passed to the action.

finish: state => ()

Optional function to terminate the runtime, receives the current state of the route as a parameter.

stop: () => ()

Calls the finish function of the runtime with the contents of the current state, and from then on any call to the change function within the runtime will be ignored.

state: object

The state is initialized by the init function or as an empty object (if init is not passed).

Listed here are the state properties that are modified with each route change. Note that plugins can also modify state properties.

url: string

The url as passed to the change function.

route: string

The route that matched as declared.

path: string

The part of the url before the ?.

Params: object

Object containing the variables declared in the route with the associated values in the current path.

query: string

The part of url after the ?.

⭐ Support

If this project was useful to you, consider giving it a star on github, it's a way to increase evidence and attract more contributors.

πŸ“¦ Projects using this module

If your project is not on the list, submit a pull request, it is a way to increase awareness of your project and this module.

  • Merlin: A functional JS framework that values elegance, simplicity and minimalism.
  • Paw: A low-code, vdom-free hyperscript framework.

πŸͺŸ Alternatives

This list of alternatives serves to find the most suitable module for your project but also and mainly to serve as a reference for implementation ideas and improvements. All contributions are welcome.

  • universal-router: A simple middleware-style router for isomorphic JavaScript web apps
  • director: A tiny and isomorphic URL router for JavaScript.
  • page.js: Micro client-side router inspired by the Express router (~1200 bytes).
  • pathjs: Simple, lightweight routing for web browsers.
  • crossroads: JavaScript Routes.
  • davis.js: RESTful degradable JavaScript routing using pushState.
  • router: A very simple router for the demo of WeUI
  • navigo: A simple vanilla JavaScript router.
  • router.js
  • slim-router: A Javascript Router based on History.js
  • router.js: Router.js is a simple and powerful javascript library to handle routing
  • router: Small and powerful client-side router for Web Components. Framework-agnostic.
  • react-router: Declarative routing for React
  • vue-router: The official router for Vue 2
  • ui-router: UI-Router for Angular: State-based routing for Angular (v2+)
  • express: Fast, unopinionated, minimalist web framework for node.
  • koa-route: Simple route middleware

🀝 Contributing

It's a very simple project. Any contribution, any feedback is greatly appreciated.