lukeed / navaid

A navigation aid (aka, router) for the browser in 850 bytes~!
MIT License
778 stars 26 forks source link

RFC: Multiple Navaid Instances? #10

Closed lukeed closed 5 years ago

lukeed commented 5 years ago

There's currently not a good way to handle multiple routers within the same page. But what are your thoughts about this for handling multiple routers in a page?

TBH I'm not even sure that this is a good idea... still on the fence

var items = (
  navaid()
    .on('/items/', () => console.log('Items Home'))
    .on('/items/:title', x => console.log('Item: ', x.title))
);

var books = (
  navaid()
    .on('/books/', () => console.log('Books Home'))
    .on('/books/:title', x => console.log('Book: ', x.title))
);

var main = (
  navaid()
    .on('/', () => console.log('Home'))
    .on('/about', () => console.log('About'))
    .use(items)
    .use(books)
    .on('/contact', () => console.log('Contact'))
);

main.listen();

I already have this built, and it seems to be the only sane way to juggle multiple routers on a page.

As for actual routing, there are two options:

1) Look at the main routes first and then the sub-routers' routes, disregarding the order of the router composition:

```
/   –>   /about   –>   /contact   –>   /items   –>   /items/:title   –>   /books   –>   /books/:title
```

2) Treat all routers' routes equally, respecting the order of composition:

```
/   –>   /about   –>   /items   –>   /items/:title   –>   /books   –>   /books/:title   –>   /contact
```

Of course, another alternative is to do something like this:

main.listen();
items.listen();
books.listen();

...which feels much simpler to the end-user, but it very likely adds a considerable amount of overhead to Navaid in order to facilitate that. IMO it may not be worth it, especially if/when the 90% use-case is to use a single instance per App.


No matter the choice, handling the on404 behavior needs answering, too. For example, if main, items, and books all have their own 404-handler, how will one know to fire vs wait for a sibling router? Similarly, what if only books had a 404-handler defined... how should it know that it's okay to run?

My hunch is that the 2nd option (3x .listen()) is not compatible with this at all.

silentworks commented 5 years ago

I'm not sure from a performance perspective how this would work out, but It would be nice if use could take a prefix for the routes it is mounting.

var items = (
  navaid()
    .on('/', () => console.log('Items Home'))
    .on('/:title', x => console.log('Item: ', x.title))
);

var books = (
  navaid()
    .on('/', () => console.log('Books Home'))
    .on('/:title', x => console.log('Book: ', x.title))
);

var main = (
  navaid()
    .on('/', () => console.log('Home'))
    .on('/about', () => console.log('About'))
    .use('items', items)
    .use('books', books)
    .on('/contact', () => console.log('Contact'))
);

main.listen();
paulocoghi commented 5 years ago

IMHO I don't think it's a good idea to have multiple instances. From 2013 until today, in all my projects, I only use/used one router instance, regardless of which routing library I have used.

I believe that the possible use cases covered by multiple router instances could also be solved with only one. But that is only my personal/particular opinion.

jacwright commented 5 years ago

A case for multiple navaid instances is when you have multiple "applications" built into 1. For example, if you have a product that several teams/individuals work on, and one part of that product is a blog, the blog team could treat it as a standalone app. They could set up navaid using relative URLs and have the base passed in from the parent container application.

In this case, each part of the app would want to be able to be run by itself (e.g. for testing) or with everything else. It would be nice if the different instances of navaid didn't have to know about each other.

The only thing I see preventing this right now is the 'click' listener. If an app (or sub-app) captured clicks at their root HTML element instead of on Window, then navaid would be able to work with multiple instances. It would also play better as an app within a page that might have links that should be outside of navaid's control.

This is what I would propose to handle this situation. https://github.com/lukeed/navaid/pull/11

Except, now that I've created it, I believe there is still an issue with 404 matching. :(

Edit: actually, in the hierarchal app example, the container app would have a route to /blog/* that would load the blog and start it's navaid instance, so the 404 stuff works here. And any app on a page that relies on the URL and ran independently would want to 404 when the URL is matching something it doesn't support. So I believe this still works. One instance of navaid shouldn't hold it's 404 callback because another instance on the page is matching.

lukeed commented 5 years ago

Thank you everyone for feedback! 🙇

This is now possible, although there is a buyer-beware notice that comes with it: https://github.com/lukeed/navaid/pull/12#issuecomment-504689963

Below is the test page I used to verify the functionality:

<script>
  var router1 = (
    navaid()
      .on('/', () => console.log('Home'))
      .on('/about', () => console.log('About'))
      .on('/foo/:name', x => console.log('foo: ', x.name))
  );

  var router2 = (
    navaid('/users')
      .on('/', () => console.log('Users Home'))
      .on('/:name', x => console.log('User: ', x.name, '::', location.search))
  );

  router1.listen();
  router2.listen();
</script>

<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/users/john">John</a>
  <a href="/users/me">Me</a>
  <a href="/users/bob?foo=123">Bob</a>
  <a href="/users/mary?bar=123">Mary</a>
</nav>

<hr>

<nav>
  <a href="foo/bar">foo/bar (relative)</a>
  <a href="xyz">xyz (relative)</a>
</nav>

I don't believe or consider this to be a primary feature or use-case for Navaid, so I don't want to necessarily promote it (mostly due to the 404 overlap) but it is possible for those who want it.