1 | <div align="center">
|
2 |
|
3 | ![yrv](Japan_road_sign_201-D.svg)
|
4 |
|
5 | ![Build status](https://github.com/pateketrueke/yrv/workflows/build/badge.svg)
|
6 | [![NPM version](https://img.shields.io/npm/v/yrv)](https://www.npmjs.com/package/yrv)
|
7 | [![Known Vulnerabilities](https://snyk.io/test/npm/yrv/badge.svg)](https://snyk.io/test/npm/yrv)
|
8 | [![donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY)
|
9 |
|
10 | </div>
|
11 |
|
12 | > The `v` is for Svelte
|
13 |
|
14 | Built on top of [abstract-nested-router](https://www.npmjs.com/package/abstract-nested-router), so you can use nested routers, also:
|
15 |
|
16 | - Advanced parameters can be used, e.g. `/:id<\d+>` — [see docs](https://www.npmjs.com/package/abstract-nested-router#params)
|
17 | - ARIA-compliant, sets `[aria-current="page"]` on active links
|
18 | - Seamless `<base href="..." />` integration
|
19 | - Conditionals and redirection through props
|
20 | - Fallback `<Route />` handlers
|
21 | - Hash and URI-based routes
|
22 | - Support for [query-string](https://www.npmjs.com/package/query-string)
|
23 | - [REPL ready!](https://svelte.dev/repl/0f07c6134b16432591a9a3a0095a80de?version=3.12.1)
|
24 |
|
25 | > `yrv` will use any _base-href_ found on the current page to rewrite links and routes.
|
26 |
|
27 | ## Usage
|
28 |
|
29 | Install `yrv` through NPM or Yarn:
|
30 |
|
31 | ```html
|
32 | <script>
|
33 | import { Router, Route, Link } from 'yrv';
|
34 | </script>
|
35 |
|
36 | <Link href="/">Home</Link>
|
37 | | <Link href="/World">Hello</Link>
|
38 | | <Link href="/not/found">NotFound</Link>
|
39 |
|
40 | <p>
|
41 | <Router>
|
42 | <Route exact>Hello World</Route>
|
43 | <Route fallback>Not found</Route>
|
44 | <Route exact path="/:name" let:router>Hey {router.params.name}!</Route>
|
45 | </Router>
|
46 | </p>
|
47 | ```
|
48 |
|
49 | > Notice `fallback` routes can’t be placed at the beginning, otherwise further routes will not be mounted. :bomb:
|
50 |
|
51 | ## Components
|
52 |
|
53 | > You MUST declare at least, one top-level `Router` to setup the bindings.
|
54 |
|
55 | ### `<Router {path} {pending} {disabled} {condition} {nofallback} />`
|
56 |
|
57 | This component will hold any given routes as children, `path` is always derived from parent routes.
|
58 |
|
59 | Available props:
|
60 |
|
61 | - `{path}` — Any segment to derive a fullpath from, defaults to `/`
|
62 | - `{pending}` — Svelte-component or String; top-level `pending` support
|
63 | - `{disabled}` — Boolean; Similar to condition, but for bound props
|
64 | - `{condition}` — Function; if given, render only if evaluates to true
|
65 | - `{nofallback}` — If set, non-matched routes will never raise a failure
|
66 |
|
67 | > 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).
|
68 |
|
69 | ### `<Route {key} {path} {exact} {pending} {fallback} {component} {disabled} {condition} {redirect} let:router />`
|
70 |
|
71 | Main container for routing, they can hold any component or children.
|
72 |
|
73 | Available props:
|
74 |
|
75 | - `{key}` — The route identity, not its path; defaults to random pseudo-hash
|
76 | - `{path}` — Any segment to derive a fullpath from, default to `/`
|
77 | - `{exact}` — If set, the route will render only if the route exactly matches
|
78 | - `{pending}` — Svelte-component or String; rendered during the loading of dynamic components
|
79 | - `{fallback}` — If set, the route will render only if no more routes were matched
|
80 | - `{component}` — Accepts either a valid svelte-component, a promise, or a dynamic import function
|
81 | - `{disabled}` — Boolean; Similar to `condition`, but for bound props
|
82 | - `{condition}` — Function; if given, the route will render only if evaluates to `true`
|
83 | - `{redirect}` — Alternate redirection location, only if the previous condition was `true`
|
84 | - `let:router` — Injects the `router` context, it also provides `failure` in case of errors
|
85 |
|
86 | > If you omit `exact`, then `/x` would match both `/` and `/x` routes — [see docs](https://www.npmjs.com/package/abstract-nested-router#params)
|
87 |
|
88 | 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).
|
89 |
|
90 | ```html
|
91 | <script>
|
92 | import SvelteComponent from 'path/to/svelte-component.svelte';
|
93 | </script>
|
94 |
|
95 | <Link href="/">Home</Link>
|
96 | | <Link href="/svelte-component">Svelte component</Link>
|
97 | | <Link href="/promise">Promised component</Link>
|
98 | | <Link href="/lazy">Lazy component</Link>
|
99 |
|
100 | <p>
|
101 | <Router>
|
102 | <Route exact>Hello World</Route>
|
103 | <Route exact path="/svelte-component" component={SvelteComponent}/>
|
104 | <Route exact path="/promise" component="{import('path/to/other-component.svelte')}"/>
|
105 | <Route exact path="/lazy" component="{() => import('path/to/another-component.svelte')}"/>
|
106 | </Router>
|
107 | </p>
|
108 | ```
|
109 |
|
110 | > 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.
|
111 |
|
112 | ### `<Link {go} {href} {open} {title} {exact} {reload} {replace} {class} />`
|
113 |
|
114 | In order to navigate, you can use `Link` components, or regular links, etc.
|
115 |
|
116 | > All `href` values MUST be absolute, only links starting with `/` or `#` are allowed.
|
117 |
|
118 | Available props:
|
119 |
|
120 | - `{go}` — History shortcut (see below)
|
121 | - `{href}` — New location, default to `/`
|
122 | - `{open}` — Same behavior as `<a target="_blank">`
|
123 | - `{title}` — HTML title-attribute value
|
124 | - `{button}` — If set, will use button-tag instead
|
125 | - `{exact}` — Determine if link should match exactly to be set as active
|
126 | - `{reload}` — Use `location.href` instead
|
127 | - `{replace}` — Use `history.replaceState()` instead
|
128 | - `{class}` — Custom class-name for the mounted anchor
|
129 |
|
130 | > 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.
|
131 |
|
132 | Normal `on:click` events are still allowed, so you can use:
|
133 |
|
134 | ```html
|
135 | <Link on:click={() => location.reload()}>Reload</Link>
|
136 | ```
|
137 |
|
138 | > Active _links_ will gain the `[aria-current]` attribute, and `[disabled]` if they're buttons.
|
139 |
|
140 | Aditionally, you can setup `go` to move around:
|
141 |
|
142 | - `"back"` — String; if given, will invoke `history.back()`
|
143 | - `"fwd"` — String; if given, will invoke `history.fwd()`
|
144 | - `n` — Number; if given, it'll be used to invoke `history.go(n)`
|
145 |
|
146 | > If navigating through `history` is not possible a normal redirection will run. :anchor:
|
147 |
|
148 | ## Public API
|
149 |
|
150 | - `navigateTo(path[, options])` — Change the location, supported options are:
|
151 | - `reload` — If true, it will use `document.location.href` instead
|
152 | - `replace` — If true, it will use `history.replaceState()` instead
|
153 | - `params` — Used to replace `:placeholders` from given path
|
154 | - `queryParams` — Additional search-params for the new location
|
155 | - `$router` — Store with shared routeInfo details, similar to `let:router`
|
156 |
|
157 | > `yrv` gracefully degrades to `location.hash` on environments where `history` is not suitable, also it can be forced through `router.hashchange = true`.
|
158 |
|
159 | ### Route Info
|
160 |
|
161 | Route changes are propagated through stores, if you want to listen too just subscribe, e.g.
|
162 |
|
163 | ```js
|
164 | import { router } from 'yrv';
|
165 |
|
166 | router.subscribe(e => {
|
167 | if (!e.initial) console.log(e);
|
168 | });
|
169 | ```
|
170 |
|
171 | Using this technique you gain access to the same detail object as `let:router` does.
|
172 |
|
173 | > Notice the `initial` property is present as soon the store is initialized, consecutive changes will not have it anymore.
|
174 |
|
175 | ### IE11 support
|
176 |
|
177 | Support for IE11 is _granted_ if you include, at least, the following polyfills before your application:
|
178 |
|
179 | ```html
|
180 | <script>if (!!window.MSInputMethodContext && !!document.documentMode)
|
181 | document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Promise,Object.getOwnPropertyDescriptors"><\/script>');</script>
|
182 | <script src="your-app.js"></script>
|
183 | ```
|
184 |
|
185 | > `document.write()` is used because conditional comments were dropped in IE10, so this way you can conditionally load polyfills anyway.
|
186 |
|
187 | Also, you MUST [enable either `buble` or `babel`](https://github.com/sveltejs/svelte/issues/2621) within your build pipeline to transpile down to ES5.
|
188 |
|
189 | ### Frequently Asked Questions
|
190 |
|
191 | **How to conditionally render a `<Router />` component?**
|
192 |
|
193 | Both Route/Router components support the `disabled` and `condition` props, but:
|
194 |
|
195 | - Use `condition` to allow/disallow route-dispatching dynamically
|
196 | - Use `disabled` to skip from rendering, it will add/remove the route
|
197 |
|
198 | This new `disabled` prop would work as you're expecting:
|
199 |
|
200 | ```html
|
201 | <Router disabled={!showNavBar}>
|
202 | ...
|
203 | </Router>
|
204 | ```
|
205 |
|
206 | **What means the `exact` property and how it works?**
|
207 |
|
208 | Say you have three routes:
|
209 |
|
210 | - `/a` (exact)
|
211 | - `/a/b` (non-exact)
|
212 | - `/a/b/c` (exact)
|
213 |
|
214 | Now, you navigate from `/a` to `/a/b/c`:
|
215 |
|
216 | - Since `/a` was active, and it was exact, `yrv` clears out the `routeInfo` for that route.
|
217 | - Since `/a/b` is not exact, `yrv` activate this route because is half-way to the final route.
|
218 |
|
219 | > If you plan to have more routes nested, then the route will never be `exact` (at least at top-levels).
|
220 |
|
221 | 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.
|
222 |
|
223 | If the link for `/a` were also `exact`, then it'll be _active_ if the matching route is `/a` only.
|
224 |
|
225 | **Why `path` can't be an empty string like other routers does?**
|
226 |
|
227 | Even if browsers treat `http://localhost:8080` and `http://localhost:8080/` as the same thing I wanted to keep paths clear as possible.
|
228 |
|
229 | Internally `yrv` normalizes any given URI to keep a trailing slash, so `/foo` is `/foo/` for matching purposes.
|
230 |
|
231 | Also, the default path is usually `/` so there's no point on having to declare anything else:
|
232 |
|
233 | ```html
|
234 | <Route>OK</Route>
|
235 | <Route path="/">OK</Route>
|
236 | ```
|
237 |
|
238 | **What is `routeInfo` and how can I access it outside routes?**
|
239 |
|
240 | This object is very similar to what you get with `let:router` inside components.
|
241 |
|
242 | Use the `$router` store to access it, e.g.
|
243 |
|
244 | ```html
|
245 | <script>
|
246 | import { router } from 'yrv';
|
247 | </script>
|
248 | <pre>{JSON.stringify($router, null, 2)}</pre>
|
249 | ```
|
250 |
|
251 | **Why does Yrv not work with Parcel or webpack/snowpack?**
|
252 |
|
253 | If you're getting any of the errors below:
|
254 |
|
255 | - store.subscribe is not a function
|
256 | - Class constructor SvelteComponent cannot be invoked without 'new'
|
257 | - 'on_outro' is not exported by [...]
|
258 | - 'target' is a required option
|
259 |
|
260 | Make sure you're using the right settings:
|
261 |
|
262 | 1. Add mainFields into resolve config, e.g. `mainFields: ['svelte', 'browser', 'module', 'main']`
|
263 | 2. Remove `exclude: /node_modules/` from `svelte-loader` config
|
264 |
|
265 | > 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.
|
266 |
|
267 | **Can I use hash-based routes _à la_ Gmail? e.g. `index.html#/profile`, `index.html#/book/42`?**
|
268 |
|
269 | Yes, URIs like that are suitable for embedded apps like Electron, where normal URLs would fail.
|
270 |
|
271 | 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!
|
272 |
|
273 | > 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.
|
274 |
|
275 | **Why I'm getting `<Component> was created with unknown prop 'router'` in the browser's console?**
|
276 |
|
277 | If you're not using the `router` prop inside your route-components then just add:
|
278 |
|
279 | ```html
|
280 | <script>
|
281 | export const router = null;
|
282 | </script>
|
283 | ```
|
284 |
|
285 | That will remove the warning and also will make `eslint-plugin-svelte3` in your workflow happy.
|
286 |
|
287 | **Why `router.subscribe` is called two times when I first open the page?**
|
288 |
|
289 | 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.
|
290 |
|
291 | > In this case, and additional property `initial` is added to identify such event.
|
292 |
|
293 | **Is there any method that allows me to detect route change?**
|
294 |
|
295 | Yes, you can subscribe to the router store, e.g. `router.subscribe(...)` — [see above](#route-info).
|
296 |
|
297 | **Is there a way to reduce the bundle size of yrv?**
|
298 |
|
299 | Since `v0.0.46` you'll be getting the most reduced version we can ship, however it comes without development warnings.
|
300 |
|
301 | > Consume it as `import { ... } from 'yrv/debug'` right away and you'll get a more complete version with included DEBUG information.
|