Awesome
<div align="center">The
v
is for Svelte
Built on top of abstract-nested-router, so you can use nested routers, also:
- Advanced parameters can be used, e.g.
/:id<\d+>
— see docs - ARIA-compliant, sets
[aria-current="page"]
on active links - Seamless
<base href="..." />
integration - Conditionals and redirection through props
- Fallback
<Route />
handlers - Hash and URI-based routes
- Support for query-string
- REPL ready!
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="/">Home</Link>
| <Link href="/World">Hello</Link>
| <Link href="/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:
{path}
— Any segment to derive a fullpath from, defaults to/
{pending}
— Svelte-component or String; top-levelpending
support{disabled}
— Boolean; Similar to condition, but for bound props{condition}
— Function; if given, render only if evaluates to true{nofallback}
— If set, non-matched routes will never raise a failure
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:
{key}
— The route identity, not its path; defaults to random pseudo-hash{path}
— Any segment to derive a fullpath from, default to/
{exact}
— If set, the route will render only if the route exactly matches{pending}
— Svelte-component or String; rendered during the loading of dynamic components{fallback}
— If set, the route will render only if no more routes were matched{component}
— Accepts either a valid svelte-component, a promise, or a dynamic import function{disabled}
— Boolean; Similar tocondition
, but for bound props{condition}
— Function; if given, the route will render only if evaluates totrue
{redirect}
— Alternate redirection location, only if the previous condition wastrue
let:router
— Injects therouter
context, it also providesfailure
in case of errors
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="/">Home</Link>
| <Link href="/svelte-component">Svelte component</Link>
| <Link href="/promise">Promised component</Link>
| <Link href="/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 throughshimport
, 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:
{go}
— History shortcut (see below){href}
— New location, default to/
{open}
— Same behavior as<a target="_blank">
{title}
— HTML title-attribute value{button}
— If set, will use button-tag instead{exact}
— Determine if link should match exactly to be set as active{reload}
— Uselocation.href
instead{replace}
— Usehistory.replaceState()
instead{class}
— Custom class-name for the mounted anchor
The value for
open
can be a string including the window specs, e.g.width=400,height=200
— aon: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:
"back"
— String; if given, will invokehistory.back()
"fwd"
— String; if given, will invokehistory.fwd()
n
— Number; if given, it'll be used to invokehistory.go(n)
If navigating through
history
is not possible a normal redirection will run. :anchor:
Public API
navigateTo(path[, options])
— Change the location, supported options are:reload
— If true, it will usedocument.location.href
insteadreplace
— If true, it will usehistory.replaceState()
insteadparams
— Used to replace:placeholders
from given pathqueryParams
— Additional search-params for the new location
$router
— Store with shared routeInfo details, similar tolet:router
yrv
gracefully degrades tolocation.hash
on environments wherehistory
is not suitable, also it can be forced throughrouter.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="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:
- Use
condition
to allow/disallow route-dispatching dynamically - Use
disabled
to skip from rendering, it will add/remove the route
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:
/a
(exact)/a/b
(non-exact)/a/b/c
(exact)
Now, you navigate from /a
to /a/b/c
:
- Since
/a
was active, and it was exact,yrv
clears out therouteInfo
for that route. - Since
/a/b
is not exact,yrv
activate this route because is half-way to the final route.
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:
- store.subscribe is not a function
- Class constructor SvelteComponent cannot be invoked without 'new'
- 'on_outro' is not exported by [...]
- 'target' is a required option
Make sure you're using the right settings:
- Add mainFields into resolve config, e.g.
mainFields: ['svelte', 'browser', 'module', 'main']
- Remove
exclude: /node_modules/
fromsvelte-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.