@keenmate/svelte-spa-router 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2019, Alessandro Segala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,507 @@
1
+ # svelte-spa-router
2
+ > This is a fork of [ItalyPaleAle/svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router).
3
+ > This fork adds option to not use hash-based routing and instead leverage `History`'s `pushState`/`replaceState`
4
+
5
+ ## No hash routing
6
+ To configure library to not use hash routing, you need to set config before App initializes (in `main.js/ts`):
7
+
8
+ ### Config preparation
9
+ ```javascript
10
+ import {HashRoutingEnabled, BasePath} from "@keenmate/svelte-spa-router"
11
+
12
+ HashRoutingEnabled.set(false)
13
+ BasePath.set(import.meta.env.BASE_URL)
14
+ ```
15
+
16
+ ### Usage
17
+ The trick is in a mandatory use of `use:link` on HTML links to augment these links to call `pushState`/`replaceState` instead of actual navigation
18
+ As a bonus, it automatically prepends `BasePath` value to these links if it is not already present
19
+
20
+ ```html
21
+ <a href="/home" use:link>
22
+ Link to home
23
+ </a>
24
+
25
+ <a href="/about" use:link>
26
+ Link to about
27
+ </a>
28
+ ```
29
+
30
+ ## Original README
31
+
32
+ This module is a router for [Svelte 3 and 4](https://github.com/sveltejs/svelte) applications, specifically optimized
33
+ for Single Page Applications (SPA).
34
+
35
+ Main features:
36
+
37
+ - Leverages **hash-based routing**, which is optimal for SPAs and doesn't require any server-side processing
38
+ - Insanely simple to use, and has a minimal footprint
39
+ - Uses the tiny [regexparam](https://github.com/lukeed/regexparam) for parsing routes, with support for parameters (e.g.
40
+ `/book/:id?`) and more
41
+
42
+ This module is released under MIT license.
43
+
44
+ ## Video
45
+
46
+ ["So you want to pick a router?"]((https://www.youtube.com/watch?v=EL1qM0cv0eA)) talk by @ItalyPaleAle at Svelte Summit
47
+ 2020. Includes an explanation of the two kinds of routers and a demo of svelte-spa-router.
48
+ _(Click on the cover image to play the video on YouTube)_
49
+
50
+ [![Click to play video: 'So you want to pick a router?'](https://img.youtube.com/vi/EL1qM0cv0eA/0.jpg)](https://www.youtube.com/watch?v=EL1qM0cv0eA)
51
+
52
+ ## Hash-based routing
53
+
54
+ With hash-based routing, navigation is possible thanks to storing the current view in the part of the URL after `#`,
55
+ called "hash" or "fragment".
56
+
57
+ For example, if your SPA is in a static file called `index.html`, your URLs for navigating within the app look something
58
+ like `index.html#/profile`, `index.html#/book/42`, etc. (The `index.html` part can usually be omitted for the index
59
+ file, so you can just create URLs that look like `http://example.com/#/profile`).
60
+
61
+ When I created this component, other routers for Svelte 3+ implemented navigation using the HTML5 history API. While
62
+ those URLs look nicer (e.g. you can actually navigate to `http://example.com/profile`), they are not ideal for static
63
+ Single Page Applications. In order for users to be able to share links or even just refresh the page, you are required
64
+ to have a server on the backend processing the request, and building fully-static apps is much harder as a consequence.
65
+
66
+ Hash-based routing is simpler, works well even without a server, and it's generally better suited for static SPAs,
67
+ especially when SEO isn't a concern, as is the case when the app requires authentication. Many popular apps use
68
+ hash-based routing, including GMail!
69
+
70
+ ## Sample code
71
+
72
+ Check out the code in the [examples](/examples) folder for some usage examples.
73
+
74
+ To run the samples, clone the repository, install the dependencies, then build each sample using Rollup:
75
+
76
+ ````sh
77
+ git clone https://github.com/ItalyPaleAle/svelte-spa-router
78
+ cd svelte-spa-router
79
+ npm install
80
+
81
+ # Navigate to a sample
82
+ cd examples/…
83
+ # For example
84
+ cd examples/basic-routing
85
+
86
+ # Build and run (in the folder of a sample)
87
+ npx rollup -c
88
+ npx serve -n -l 5050 dist
89
+ ````
90
+
91
+ The sample will be running at http://localhost:5050
92
+
93
+ ## Starter template
94
+
95
+ You can find a starter template with Svelte 4 and svelte-spa-router
96
+ at [italypaleale/svelte-spa-router-template](https://github.com/italypaleale/svelte-spa-router-template).
97
+
98
+ To use the template:
99
+
100
+ ```sh
101
+ npx degit italypaleale/svelte-spa-router-template svelte-app
102
+ cd svelte-app
103
+ ```
104
+
105
+ More information can be found on the [template's repo](https://github.com/italypaleale/svelte-spa-router-template).
106
+
107
+ ## Using svelte-spa-router
108
+
109
+ You can include the router in any project using Svelte 3 or 4.
110
+
111
+ ### Install from NPM
112
+
113
+ To add svelte-spa-router to your project:
114
+
115
+ ````sh
116
+ npm install svelte-spa-router
117
+ ````
118
+
119
+ ### Supported browsers
120
+
121
+ svelte-spa-router aims to support modern browsers, including recent versions of:
122
+
123
+ - Chrome
124
+ - Edge ("traditional" and Chromium-based)
125
+ - Firefox
126
+ - Safari
127
+
128
+ Support for Internet Explorer is not a goal for this project. Some users have reportedly been able to use
129
+ svelte-spa-router with IE11 after transpilation (e.g. with Babel), but this is not guaranteed.
130
+
131
+ ### Define your routes
132
+
133
+ Each route is a normal Svelte component, with the markup, scripts, bindings, etc. Any Svelte component can be a route.
134
+
135
+ The route definition is just a JavaScript dictionary (object) where the key is a string with the path (including
136
+ parameters, etc), and the value is the route object.
137
+
138
+ For example:
139
+
140
+ ````js
141
+ import Home from './routes/Home.svelte'
142
+ import Author from './routes/Author.svelte'
143
+ import Book from './routes/Book.svelte'
144
+ import NotFound from './routes/NotFound.svelte'
145
+
146
+ const routes = {
147
+ // Exact path
148
+ '/': Home,
149
+
150
+ // Using named parameters, with last being optional
151
+ '/author/:first/:last?': Author,
152
+
153
+ // Wildcard parameter
154
+ '/book/*': Book,
155
+
156
+ // Catch-all
157
+ // This is optional, but if present it must be the last
158
+ '*': NotFound,
159
+ }
160
+ ````
161
+
162
+ Routes must begin with `/` (or `*` for the catch-all route).
163
+
164
+ Alternatively, you can also define your routes using custom regular expressions, as explained below.
165
+
166
+ Note that the order matters! When your users navigate inside the app, the first matching path will determine which route
167
+ to load. It's important that you leave any "catch-all" route (e.g. a "Page not found" one) at the end.
168
+
169
+ ### Include the router view
170
+
171
+ To display the router, in a Svelte component (usually `App.svelte`), first import the router component:
172
+
173
+ ````js
174
+ import Router from 'svelte-spa-router'
175
+ ````
176
+
177
+ Then, display the router anywhere you'd like by placing the component in the markup. For example:
178
+
179
+ ````svelte
180
+ <body>
181
+ <Router {routes}/>
182
+ </body>
183
+ ````
184
+
185
+ The `routes` prop is the dictionary defined above.
186
+
187
+ That's it! You already have all that you need for a fully-functional routing experience.
188
+
189
+ ### Dynamically-imported components and code-splitting
190
+
191
+ Starting with version 3.0, svelte-spa-router supports dynamically-imported components (via the `import()` construct).
192
+ The advantage of using dynamic imports is that, if your bundler supports that, you can enable code-splitting and reduce
193
+ the size of the bundle you send to your users. This has been tested with bundlers including Rollup and Webpack.
194
+
195
+ To use dynamically-imported components, you need to leverage the `wrap` method (which can be used for a variety of
196
+ actions, as per the docs on [route wrapping](/Advanced%20Usage.md#route-wrapping)). First, import the `wrap` method:
197
+
198
+ ```js
199
+ import {wrap} from 'svelte-spa-router/wrap'
200
+ ```
201
+
202
+ Then, in your route definition, wrap your routes using the `wrap` method, passing a function that returns the
203
+ dynamically-imported component to the `asyncComponent` property:
204
+
205
+ ```js
206
+ wrap({
207
+ asyncComponent: () => import('./Foo.svelte')
208
+ })
209
+ ```
210
+
211
+ > Note: the value of `asyncComponent` must be the **definition of a function** returning a dynamically-imported
212
+ > component, such as `asyncComponent: () => import('./Foo.svelte')`.
213
+ > Do **not** use `asyncComponent: import('./Foo.svelte')`, which is a function invocation instead.
214
+
215
+ For example, to make the Author and Book routes from the first example dynamically-imported, we'd update the code to:
216
+
217
+ ````js
218
+ // Import the wrap method
219
+ import {wrap} from 'svelte-spa-router/wrap'
220
+
221
+ // Note that Author and Book are not imported here anymore, so they can be imported at runtime
222
+ import Home from './routes/Home.svelte'
223
+ import NotFound from './routes/NotFound.svelte'
224
+
225
+ const routes = {
226
+ '/': Home,
227
+
228
+ // Wrapping the Author component
229
+ '/author/:first/:last?': wrap({
230
+ asyncComponent: () => import('./routes/Author.svelte')
231
+ }),
232
+
233
+ // Wrapping the Book component
234
+ '/book/*': wrap({
235
+ asyncComponent: () => import('./routes/Book.svelte')
236
+ }),
237
+
238
+ // Catch-all route last
239
+ '*': NotFound,
240
+ }
241
+ ````
242
+
243
+ The `wrap` method accepts an object with multiple properties and enables other features, including: setting a "loading"
244
+ component that is shown while a dynamically-imported component is being requested, adding pre-conditions (route guards),
245
+ passing static props, and adding custom user data.
246
+
247
+ You can learn more about all the features of `wrap` in the documentation
248
+ for [route wrapping](/Advanced%20Usage.md#route-wrapping).
249
+
250
+ ### Navigating between pages
251
+
252
+ You can navigate between pages with normal anchor (`<a>`) tags. For example:
253
+
254
+ ````svelte
255
+ <a href="#/book/123">Thus Spoke Zarathustra</a>
256
+ ````
257
+
258
+ #### The `use:link` action
259
+
260
+ Rather than having to type `#` before each link, you can also use the `use:link` action:
261
+
262
+ ````svelte
263
+ <script>
264
+ import {link} from 'svelte-spa-router'
265
+ </script>
266
+ <a href="/book/321" use:link>The Little Prince</a>
267
+ ````
268
+
269
+ The `use:link` action accepts an optional parameter `opts`, which can be one of:
270
+
271
+ - A dictionary `{href: '/foo', disabled: false}` where both keys are optional:
272
+ - If you set a value for `href`, your link will be updated to point to that address, reactively (this will always take
273
+ precedence over `href` attributes, if present)
274
+ - Setting `disabled: true` disables the link, so clicking on that would have no effect
275
+ - A string with a destination (e.g. `/foo`), which is a shorthand to setting `{href: '/foo'}`.
276
+
277
+ For example:
278
+
279
+ ````svelte
280
+ <script>
281
+ import {link} from 'svelte-spa-router'
282
+ let myLink = "/book/456"
283
+ </script>
284
+ <!-- Note the {{...}} notation because we're passing an object as parameter for a Svelte action -->
285
+ <a use:link={{href: myLink, disabled: false}}>The Biggest Princess</a>
286
+ ````
287
+
288
+ The above is equivalent to:
289
+
290
+ ````svelte
291
+ <script>
292
+ import {link} from 'svelte-spa-router'
293
+ let myLink = "/book/456"
294
+ </script>
295
+ <a use:link={myLink}>The Biggest Princess</a>
296
+ ````
297
+
298
+ Changing the value of `myLink` will reactively update the link's `href` attribute.
299
+
300
+ #### Navigating programmatically
301
+
302
+ You can navigate between pages programmatically too:
303
+
304
+ ````js
305
+ import {push, pop, replace} from 'svelte-spa-router'
306
+
307
+ // The push(url) method navigates to another page, just like clicking on a link
308
+ push('/book/42')
309
+
310
+ // The pop() method is equivalent to hitting the back button in the browser
311
+ pop()
312
+
313
+ // The replace(url) method navigates to a new page, but without adding a new entry in the browser's history stack
314
+ // So, clicking on the back button in the browser would not lead to the page users were visiting before the call to replace()
315
+ replace('/book/3')
316
+ ````
317
+
318
+ These methods can be used inside Svelte markup too, for example:
319
+
320
+ ````svelte
321
+ <button on:click={() => push('/page')}>Go somewhere</button>
322
+ ````
323
+
324
+ The `push`, `pop` and `replace` methods perform navigation actions only in the next iteration ("tick") of the JavaScript
325
+ event loop. This makes it safe to use them also inside `onMount` callbacks within Svelte components.
326
+
327
+ These functions return a Promise that resolves with no value once the navigation has been triggered (in the next tick of
328
+ the event loop); however, please note that this will likely be before the new page has rendered.
329
+
330
+ ### Parameters from routes
331
+
332
+ svelte-spa-router uses [regexparam](https://github.com/lukeed/regexparam) to parse routes, so you can add optional
333
+ parameters to the route. Basic syntax is:
334
+
335
+ - `/path` matches `/path` exactly (and only that)
336
+ - `/path/:id` matches `/path/` followed by any string, which is a named argument `id`
337
+ - `/path/:id/:version?` allows for an optional second named argument `version`
338
+ - `/path/*` matches `/path/` followed by anything, using a non-named argument
339
+
340
+ _Please refer to the documentation of regexparam for more details._
341
+
342
+ If your route contains any parameter, they will be made available to your component inside the `params` dictionary.
343
+
344
+ For example, for a route `/name/:first/:last?`, you can create this Svelte component:
345
+
346
+ ````svelte
347
+ <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
348
+ <script>
349
+ // You need to define the component prop "params"
350
+ export let params = {}
351
+ </script>
352
+ ````
353
+
354
+ Non-named arguments are returned as `params.wild`.
355
+
356
+ ### Getting the current page
357
+
358
+ You can get the current page from the `$location` readable store. This is a Svelte store, so it can be used reactively
359
+ too.
360
+
361
+ ````svelte
362
+ <script>
363
+ import {location} from 'svelte-spa-router'
364
+ </script>
365
+ <p>The current page is: {$location}</p>
366
+ ````
367
+
368
+ ### Querystring parameters
369
+
370
+ You can also extract "querystring" parameters from the hash of the page. This isn't the _real_ querystring, as it's
371
+ located after the `#` character in the URL, but it can be used in a similar way. For example:
372
+ `#/books?show=authors,titles&order=1`.
373
+
374
+ When svelte-spa-router finds a "querystring" in the hash, it separates that from the location and returns it as a string
375
+ in the Svelte store `$querystring`. For example:
376
+
377
+ ````svelte
378
+ <script>
379
+ import {location, querystring} from 'svelte-spa-router'
380
+ </script>
381
+ <p>The current page is: {$location}</p>
382
+ <p>The querystring is: {$querystring}</p>
383
+ ````
384
+
385
+ With the example above, this would print:
386
+
387
+ ````text
388
+ The current page is: /books
389
+ The querystring is: show=authors,titles&order=1
390
+ ````
391
+
392
+ It's important to note that, to keep this component lightweight, svelte-spa-router **does not parse** the "querystring".
393
+ If you want to parse the value of `$querystring`, you can
394
+ use [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) available in all modern
395
+ browsers, or third-party modules such as [qs](https://www.npmjs.com/package/qs).
396
+
397
+ ### Highlight active links
398
+
399
+ svelte-spa-router has built-in support for automatically marking links as "active", with the `use:active` action.
400
+
401
+ For example, you can use the code below to add the CSS class `active` to links that are active:
402
+
403
+ ````svelte
404
+ <script>
405
+ import {link} from 'svelte-spa-router'
406
+ import active from 'svelte-spa-router/active'
407
+ </script>
408
+
409
+ <style>
410
+ /* Style for "active" links; need to mark this :global because the router adds the class directly */
411
+ :global(a.active) {
412
+ color: red;
413
+ }
414
+ </style>
415
+
416
+ <a href="/hello/user" use:link use:active={{path: '/hello/*', className: 'active', inactiveClassName: 'inactive'}}>Say hi!</a>
417
+ <a href="/hello/user" use:link use:active={'/hello/*'}>Say hi with a default className!</a>
418
+ <a href="/hello/user" use:link use:active>Say hi with all default options!</a>
419
+ ````
420
+
421
+ The `active` action accepts a dictionary `options` as argument:
422
+
423
+ - `options.path`: the path that, when matched, makes the link active. In the first example above, we want the link to be
424
+ active when the route is `/hello/*` (the asterisk matches anything after that). As you can see, this doesn't have to
425
+ be the same as the path the link points to. When `options.path` is omitted or false-y, it defaults to the path
426
+ specified in the link's `href` attribute. This parameter can also be a regular expression that will mark the link as
427
+ active when it matches: for example, setting to the regular expression `/^\/*\/hi$/` will make the link active when it
428
+ starts with `/` and ends with `/hi`, regardless of what's in between.
429
+ - `options.className`: the name of the CSS class to add. This is optional, and it defaults to `active` if not present.
430
+ - `options.inactiveClassName`: the name of the CSS class to add when the link is _not_ active. This is optional, and it
431
+ defaults to nothing if not present.
432
+
433
+ As a shorthand, instead of passing a dictionary as `options`, you can pass a single string or regular expression that
434
+ will be interpreted as `options.path`.
435
+
436
+ ### Define routes with custom regular expressions
437
+
438
+ Since version 1.2 of svelte-spa-router, it's possible to define routes using custom regular expressions too, allowing
439
+ for greater flexibility. However, this requires defining routes using a JavaScript Map rather than an object:
440
+
441
+ ````js
442
+ import Home from './routes/Home.svelte'
443
+ import Name from './routes/Name.svelte'
444
+ import NotFound from './routes/NotFound.svelte'
445
+
446
+ const routes = new Map()
447
+
448
+ // You can still use strings to define routes
449
+ routes.set('/', Home)
450
+ routes.set('/hello/:first/:last?', Name)
451
+
452
+ // The keys for the next routes are regular expressions
453
+ // You will very likely always want to start the regular expression with ^
454
+ routes.set(/^\/hola\/(.*)/i, Name)
455
+ routes.set(/^\/buongiorno(\/([a-z]+))/i, Name)
456
+
457
+ // Catch-all, must be last
458
+ routes.set('*', NotFound)
459
+ ````
460
+
461
+ When you define routes as regular expressions, the `params` prop is populated with an array with the result of the
462
+ matches from the regular expression.
463
+
464
+ For example, with this `Name.svelte` route:
465
+
466
+ ````svelte
467
+ <p>Params is: <code>{JSON.stringify(params)}</code></p>
468
+ <script>
469
+ // You need to define the component prop "params"
470
+ export let params = {}
471
+ </script>
472
+ ````
473
+
474
+ When visiting `#/hola/amigos`, the params prop will be `["/hola/amigos","amigos"]`.
475
+
476
+ This is consistent with the results of [
477
+ `RegExp.prototype.exec()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
478
+
479
+ > When defining a route using a regular expression, you can optionally
480
+ > use [named capturing groups](https://2ality.com/2017/05/regexp-named-capture-groups.html). When using those, in addition
481
+ > to finding your matches in the `params` prop, you can find the matches for named capturing groups in `params.group`.
482
+ > For example, consider the route:
483
+ >
484
+ > ```js
485
+ > routes.set(/^\/book\/(?<title>[a-z]+)$/, Book)
486
+ > ```
487
+ >
488
+ > When visiting `/#/book/mytitle`, the `params` prop will be an array with `["/book/mytitle", "mytitle"]`, and
489
+ `params.groups` will be a dictionary with `{"title": "mytitle"}`.
490
+
491
+ ## Advanced usage
492
+
493
+ Check out the [Advanced Usage](/Advanced%20Usage.md) documentation for using:
494
+
495
+ - [Route wrapping](/Advanced%20Usage.md#route-wrapping), including:
496
+ - [Dynamically-imported routes and placeholders](/Advanced%20Usage.md#async-routes-and-loading-placeholders)
497
+ - [Route pre-conditions](/Advanced%20Usage.md#route-pre-conditions) ("route guards")
498
+ - [Adding user data to routes](/Advanced%20Usage.md#user-data)
499
+ - [Static props](/Advanced%20Usage.md#static-props)
500
+ - [`routeEvent` event](/Advanced%20Usage.md#routeevent-event)
501
+ - [`routeLoading` and `routeLoaded` events](/Advanced%20Usage.md#routeloading-and-routeloaded-events)
502
+ - [Querystring parsing](/Advanced%20Usage.md#querystring-parsing)
503
+ - [Static props](/Advanced%20Usage.md#static-props)
504
+ - [Route transitions](/Advanced%20Usage.md#route-transitions)
505
+ - [Nested routers](/Advanced%20Usage.md#nested-routers)
506
+ - [Route groups](/Advanced%20Usage.md#route-groups)
507
+ - [Restore scroll position](/Advanced%20Usage.md#restore-scroll-position)