pateketrueke / yrv

Your routing vibes! (for Svelte)
https://svelte.dev/repl/0f07c6134b16432591a9a3a0095a80de
161 stars 9 forks source link
router svelte
![yrv](Japan_road_sign_201-D.svg) ![Build status](https://github.com/pateketrueke/yrv/workflows/build/badge.svg) [![NPM version](https://img.shields.io/npm/v/yrv)](https://www.npmjs.com/package/yrv) [![Known Vulnerabilities](https://snyk.io/test/npm/yrv/badge.svg)](https://snyk.io/test/npm/yrv) [![donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY)

The v is for Svelte

Built on top of abstract-nested-router, so you can use nested routers, also:

yrv will use any base-href found on the current page to rewrite links and routes.

Usage

Install yrv through NPM or Yarn:

<script>
  import { Router, Route, Link } from 'yrv';
</script>

<Link href="https://github.com/pateketrueke/yrv/blob/master/">Home</Link>
| <Link href="https://github.com/pateketrueke/yrv/blob/master/World">Hello</Link>
| <Link href="https://github.com/pateketrueke/yrv/blob/master/not/found">NotFound</Link>

<p>
  <Router>
    <Route exact>Hello World</Route>
    <Route fallback>Not found</Route>
    <Route exact path="/:name" let:router>Hey {router.params.name}!</Route>
  </Router>
</p>

Notice fallback routes can’t be placed at the beginning, otherwise further routes will not be mounted. :bomb:

Components

You MUST declare at least, one top-level Router to setup the bindings.

<Router {path} {pending} {disabled} {condition} {nofallback} />

This component will hold any given routes as children, path is always derived from parent routes.

Available props:

Nested routers does not need the same path to be declared inside, e.g. if the router for /top has a /sub router inside, inner router will use the route /top/sub, (the same as declaring /top/sub route outside the parent router).

<Route {key} {path} {exact} {pending} {fallback} {component} {disabled} {condition} {redirect} let:router />

Main container for routing, they can hold any component or children.

Available props:

If you omit exact, then /x would match both / and /x routes — see docs

When yrv adds a new route, it'll use any given key from its props — once routes are detached they're also removed from the router registry, due to that, the next time the same route is mounted a new key is generated (if isn't present already).

<script>
  import SvelteComponent from 'path/to/svelte-component.svelte';
</script>

<Link href="https://github.com/pateketrueke/yrv/blob/master/">Home</Link>
| <Link href="https://github.com/pateketrueke/yrv/blob/master/svelte-component">Svelte component</Link>
| <Link href="https://github.com/pateketrueke/yrv/blob/master/promise">Promised component</Link>
| <Link href="https://github.com/pateketrueke/yrv/blob/master/lazy">Lazy component</Link>

<p>
  <Router>
    <Route exact>Hello World</Route>
    <Route exact path="/svelte-component" component={SvelteComponent}/>
    <Route exact path="/promise" component="{import('path/to/other-component.svelte')}"/>
    <Route exact path="/lazy" component="{() => import('path/to/another-component.svelte')}"/>
  </Router>
</p>

Behind the scenes, for making dynamic-imports work, the bundler should inline them or just write-out the required chunks to make it work natively (<script type="module" />) or through shimport, etc.

<Link {go} {href} {open} {title} {exact} {reload} {replace} {class} />

In order to navigate, you can use Link components, or regular links, etc.

All href values MUST be absolute, only links starting with / or # are allowed.

Available props:

The value for open can be a string including the window specs, e.g. width=400,height=200 — a on:close event will be fired once the opened window is closed.

Normal on:click events are still allowed, so you can use:

<Link on:click={() => location.reload()}>Reload</Link>

Active links will gain the [aria-current] attribute, and [disabled] if they're buttons.

Aditionally, you can setup go to move around:

If navigating through history is not possible a normal redirection will run. :anchor:

Public API

yrv gracefully degrades to location.hash on environments where history is not suitable, also it can be forced through router.hashchange = true.

Route Info

Route changes are propagated through stores, if you want to listen too just subscribe, e.g.

import { router } from 'yrv';

router.subscribe(e => {
  if (!e.initial) console.log(e);
});

Using this technique you gain access to the same detail object as let:router does.

Notice the initial property is present as soon the store is initialized, consecutive changes will not have it anymore.

IE11 support

Support for IE11 is granted if you include, at least, the following polyfills before your application:

<script>if (!!window.MSInputMethodContext && !!document.documentMode)
  document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Promise,Object.getOwnPropertyDescriptors"><\/script>');</script>
<script src="https://github.com/pateketrueke/yrv/raw/master/your-app.js"></script>

document.write() is used because conditional comments were dropped in IE10, so this way you can conditionally load polyfills anyway.

Also, you MUST enable either buble or babel within your build pipeline to transpile down to ES5.

Frequently Asked Questions

How to conditionally render a <Router /> component?

Both Route/Router components support the disabled and condition props, but:

This new disabled prop would work as you're expecting:

<Router disabled={!showNavBar}>
  ...
</Router>

What means the exact property and how it works?

Say you have three routes:

Now, you navigate from /a to /a/b/c:

If you plan to have more routes nested, then the route will never be exact (at least at top-levels).

This is also true for <Link /> components — as soon as they match the [aria-current] attribute will be added on them to denote active links.

If the link for /a were also exact, then it'll be active if the matching route is /a only.

Why path can't be an empty string like other routers does?

Even if browsers treat http://localhost:8080 and http://localhost:8080/ as the same thing I wanted to keep paths clear as possible.

Internally yrv normalizes any given URI to keep a trailing slash, so /foo is /foo/ for matching purposes.

Also, the default path is usually / so there's no point on having to declare anything else:

<Route>OK</Route>
<Route path="/">OK</Route>

What is routeInfo and how can I access it outside routes?

This object is very similar to what you get with let:router inside components.

Use the $router store to access it, e.g.

<script>
  import { router } from 'yrv';
</script>
<pre>{JSON.stringify($router, null, 2)}</pre>

Why does Yrv not work with Parcel or webpack/snowpack?

If you're getting any of the errors below:

Make sure you're using the right settings:

  1. Add mainFields into resolve config, e.g. mainFields: ['svelte', 'browser', 'module', 'main']
  2. Remove exclude: /node_modules/ from svelte-loader config

If you're using an online tool that is not the official Svelte REPL the behavior is unexpected and no further support will be granted.

Can I use hash-based routes à la Gmail? e.g. index.html#/profile, index.html#/book/42?

Yes, URIs like that are suitable for embedded apps like Electron, where normal URLs would fail.

Also this mode is the default used on the Svelte REPL, because is not an iframe, nor a regular webpage... it's a weird thing!

If you enable router.hashchange = true all your regular links will be automatically rewritten to hash-based URIs instead, see how it works in our test suite.

Why I'm getting <Component> was created with unknown prop 'router' in the browser's console?

If you're not using the router prop inside your route-components then just add:

<script>
  export const router = null;
</script>

That will remove the warning and also will make eslint-plugin-svelte3 in your workflow happy.

Why router.subscribe is called two times when I first open the page?

Any subscription to stores will fire twice as they have an initial value, once the router resolves (e.g. the initial route) then a second event is fired.

In this case, and additional property initial is added to identify such event.

Is there any method that allows me to detect route change?

Yes, you can subscribe to the router store, e.g. router.subscribe(...)see above.

Is there a way to reduce the bundle size of yrv?

Since v0.0.46 you'll be getting the most reduced version we can ship, however it comes without development warnings.

Consume it as import { ... } from 'yrv/debug' right away and you'll get a more complete version with included DEBUG information.