Code Monkey home page Code Monkey logo

svelte-spa-router's Introduction

Svelte 3 Up and Running

Want to learn Svelte 3 and how to build a Single-Page App (SPA) with it (and with this router)? Check out my book Svelte 3 Up and Running on Amazon.

svelte-spa-router

Build Status npm GitHub

This module is a router for Svelte 3 and 4 applications, specifically optimized for Single Page Applications (SPA).

Main features:

  • Leverages hash-based routing, which is optimal for SPAs and doesn't require any server-side processing
  • Insanely simple to use, and has a minimal footprint
  • Uses the tiny regexparam for parsing routes, with support for parameters (e.g. /book/:id?) and more

This module is released under MIT license.

Video

"So you want to pick a router?" talk by @ItalyPaleAle at Svelte Summit 2020. Includes an explanation of the two kinds of routers and a demo of svelte-spa-router.
(Click on the cover image to play the video on YouTube)

Click to play video: 'So you want to pick a router?'

Hash-based routing

With hash-based routing, navigation is possible thanks to storing the current view in the part of the URL after #, called "hash" or "fragment".

For example, if your SPA is in a static file called index.html, your URLs for navigating within the app look something like index.html#/profile, index.html#/book/42, etc. (The index.html part can usually be omitted for the index file, so you can just create URLs that look like http://example.com/#/profile).

When I created this component, other routers for Svelte 3+ implemented navigation using the HTML5 history API. While those URLs look nicer (e.g. you can actually navigate to http://example.com/profile), they are not ideal for static Single Page Applications. In order for users to be able to share links or even just refresh the page, you are required to have a server on the backend processing the request, and building fully-static apps is much harder as a consequence.

Hash-based routing is simpler, works well even without a server, and it's generally better suited for static SPAs, especially when SEO isn't a concern, as is the case when the app requires authentication. Many popular apps use hash-based routing, including GMail!

Sample code

Check out the code in the examples folder for some usage examples.

To run the samples, clone the repository, install the dependencies, then build each sample using Rollup:

git clone https://github.com/ItalyPaleAle/svelte-spa-router
cd svelte-spa-router
npm install

# Navigate to a sample
cd examples/…
# For example
cd examples/basic-routing

# Build and run (in the folder of a sample)
npx rollup -c
npx serve -n -l 5050 dist

The sample will be running at http://localhost:5050

Starter template

You can find a starter template with Svelte 4 and svelte-spa-router at italypaleale/svelte-spa-router-template.

To use the template:

npx degit italypaleale/svelte-spa-router-template svelte-app
cd svelte-app

More information can be found on the template's repo.

Using svelte-spa-router

You can include the router in any project using Svelte 3 or 4.

Install from NPM

To add svelte-spa-router to your project:

npm install svelte-spa-router

Supported browsers

svelte-spa-router aims to support modern browsers, including recent versions of:

  • Chrome
  • Edge ("traditional" and Chromium-based)
  • Firefox
  • Safari

Support for Internet Explorer is not a goal for this project. Some users have reportedly been able to use svelte-spa-router with IE11 after transpilation (e.g. with Babel), but this is not guaranteed.

Define your routes

Each route is a normal Svelte component, with the markup, scripts, bindings, etc. Any Svelte component can be a route.

The route definition is just a JavaScript dictionary (object) where the key is a string with the path (including parameters, etc), and the value is the route object.

For example:

import Home from './routes/Home.svelte'
import Author from './routes/Author.svelte'
import Book from './routes/Book.svelte'
import NotFound from './routes/NotFound.svelte'

const routes = {
    // Exact path
    '/': Home,

    // Using named parameters, with last being optional
    '/author/:first/:last?': Author,

    // Wildcard parameter
    '/book/*': Book,

    // Catch-all
    // This is optional, but if present it must be the last
    '*': NotFound,
}

Routes must begin with / (or * for the catch-all route).

Alternatively, you can also define your routes using custom regular expressions, as explained below.

Note that the order matters! When your users navigate inside the app, the first matching path will determine which route to load. It's important that you leave any "catch-all" route (e.g. a "Page not found" one) at the end.

Include the router view

To display the router, in a Svelte component (usually App.svelte), first import the router component:

import Router from 'svelte-spa-router'

Then, display the router anywhere you'd like by placing the component in the markup. For example:

<body>
    <Router {routes}/>
</body>

The routes prop is the dictionary defined above.

That's it! You already have all that you need for a fully-functional routing experience.

Dynamically-imported components and code-splitting

Starting with version 3.0, svelte-spa-router supports dynamically-imported components (via the import() construct). The advantage of using dynamic imports is that, if your bundler supports that, you can enable code-splitting and reduce the size of the bundle you send to your users. This has been tested with bundlers including Rollup and Webpack.

To use dynamically-imported components, you need to leverage the wrap method (which can be used for a variety of actions, as per the docs on route wrapping). First, import the wrap method:

import {wrap} from 'svelte-spa-router/wrap'

Then, in your route definition, wrap your routes using the wrap method, passing a function that returns the dynamically-imported component to the asyncComponent property:

wrap({
    asyncComponent: () => import('./Foo.svelte')
})

Note: the value of asyncComponent must be the definition of a function returning a dynamically-imported component, such as asyncComponent: () => import('./Foo.svelte').
Do not use asyncComponent: import('./Foo.svelte'), which is a function invocation instead.

For example, to make the Author and Book routes from the first example dynamically-imported, we'd update the code to:

// Import the wrap method
import {wrap} from 'svelte-spa-router/wrap'

// Note that Author and Book are not imported here anymore, so they can be imported at runtime
import Home from './routes/Home.svelte'
import NotFound from './routes/NotFound.svelte'

const routes = {
    '/': Home,

    // Wrapping the Author component
    '/author/:first/:last?': wrap({
        asyncComponent: () => import('./routes/Author.svelte')
    }),

    // Wrapping the Book component
    '/book/*': wrap({
        asyncComponent: () => import('./routes/Book.svelte')
    }),

    // Catch-all route last
    '*': NotFound,
}

The wrap method accepts an object with multiple properties and enables other features, including: setting a "loading" component that is shown while a dynamically-imported component is being requested, adding pre-conditions (route guards), passing static props, and adding custom user data.

You can learn more about all the features of wrap in the documentation for route wrapping.

Navigating between pages

You can navigate between pages with normal anchor (<a>) tags. For example:

<a href="#/book/123">Thus Spoke Zarathustra</a>

The use:link action

Rather than having to type # before each link, you can also use the use:link action:

<script>
import {link} from 'svelte-spa-router'
</script>
<a href="/book/321" use:link>The Little Prince</a>

The use:link action accepts an optional parameter opts, which can be one of:

  • A dictionary {href: '/foo', disabled: false} where both keys are optional:
    • If you set a value for href, your link will be updated to point to that address, reactively (this will always take precedence over href attributes, if present)
    • Setting disabled: true disables the link, so clicking on that would have no effect
  • A string with a destination (e.g. /foo), which is a shorthand to setting {href: '/foo'}.

For example:

<script>
import {link} from 'svelte-spa-router'
let myLink = "/book/456"
</script>
<!-- Note the {{...}} notation because we're passing an object as parameter for a Svelte action -->
<a use:link={{href: myLink, disabled: false}}>The Biggest Princess</a>

The above is equivalent to:

<script>
import {link} from 'svelte-spa-router'
let myLink = "/book/456"
</script>
<a use:link={myLink}>The Biggest Princess</a>

Changing the value of myLink will reactively update the link's href attribute.

Navigating programmatically

You can navigate between pages programmatically too:

import {push, pop, replace} from 'svelte-spa-router'

// The push(url) method navigates to another page, just like clicking on a link
push('/book/42')

// The pop() method is equivalent to hitting the back button in the browser
pop()

// The replace(url) method navigates to a new page, but without adding a new entry in the browser's history stack
// So, clicking on the back button in the browser would not lead to the page users were visiting before the call to replace()
replace('/book/3')

These methods can be used inside Svelte markup too, for example:

<button on:click={() => push('/page')}>Go somewhere</button>

The push, pop and replace methods perform navigation actions only in the next iteration ("tick") of the JavaScript event loop. This makes it safe to use them also inside onMount callbacks within Svelte components.

These functions return a Promise that resolves with no value once the navigation has been triggered (in the next tick of the event loop); however, please note that this will likely be before the new page has rendered.

Parameters from routes

svelte-spa-router uses regexparam to parse routes, so you can add optional parameters to the route. Basic syntax is:

  • /path matches /path exactly (and only that)
  • /path/:id matches /path/ followed by any string, which is a named argument id
  • /path/:id/:version? allows for an optional second named argument version
  • /path/* matches /path/ followed by anything, using a non-named argument

Please refer to the documentation of regexparam for more details.

If your route contains any parameter, they will be made available to your component inside the params dictionary.

For example, for a route /name/:first/:last?, you can create this Svelte component:

<p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
<script>
// You need to define the component prop "params"
export let params = {}
</script>

Non-named arguments are returned as params.wild.

Getting the current page

You can get the current page from the $location readable store. This is a Svelte store, so it can be used reactively too.

<script>
import {location} from 'svelte-spa-router'
</script>
<p>The current page is: {$location}</p>

Querystring parameters

You can also extract "querystring" parameters from the hash of the page. This isn't the real querystring, as it's located after the # character in the URL, but it can be used in a similar way. For example: #/books?show=authors,titles&order=1.

When svelte-spa-router finds a "querystring" in the hash, it separates that from the location and returns it as a string in the Svelte store $querystring. For example:

<script>
import {location, querystring} from 'svelte-spa-router'
</script>
<p>The current page is: {$location}</p>
<p>The querystring is: {$querystring}</p>

With the example above, this would print:

The current page is: /books
The querystring is: show=authors,titles&order=1

It's important to note that, to keep this component lightweight, svelte-spa-router does not parse the "querystring". If you want to parse the value of $querystring, you can use URLSearchParams available in all modern browsers, or third-party modules such as qs.

Highlight active links

svelte-spa-router has built-in support for automatically marking links as "active", with the use:active action.

For example, you can use the code below to add the CSS class active to links that are active:

<script>
import {link} from 'svelte-spa-router'
import active from 'svelte-spa-router/active'
</script>

<style>
/* Style for "active" links; need to mark this :global because the router adds the class directly */
:global(a.active) {
    color: red;
}
</style>

<a href="/hello/user" use:link use:active={{path: '/hello/*', className: 'active', inactiveClassName: 'inactive'}}>Say hi!</a>
<a href="/hello/user" use:link use:active={'/hello/*'}>Say hi with a default className!</a>
<a href="/hello/user" use:link use:active>Say hi with all default options!</a>

The active action accepts a dictionary options as argument:

  • options.path: the path that, when matched, makes the link active. In the first example above, we want the link to be active when the route is /hello/* (the asterisk matches anything after that). As you can see, this doesn't have to be the same as the path the link points to. When options.path is omitted or false-y, it defaults to the path specified in the link's href attribute. This parameter can also be a regular expression that will mark the link as active when it matches: for example, setting to the regular expression /^\/*\/hi$/ will make the link active when it starts with / and ends with /hi, regardless of what's in between.
  • options.className: the name of the CSS class to add. This is optional, and it defaults to active if not present.
  • options.inactiveClassName: the name of the CSS class to add when the link is not active. This is optional, and it defaults to nothing if not present.

As a shorthand, instead of passing a dictionary as options, you can pass a single string or regular expression that will be interpreted as options.path.

Define routes with custom regular expressions

Since version 1.2 of svelte-spa-router, it's possible to define routes using custom regular expressions too, allowing for greater flexibility. However, this requires defining routes using a JavaScript Map rather than an object:

import Home from './routes/Home.svelte'
import Name from './routes/Name.svelte'
import NotFound from './routes/NotFound.svelte'

const routes = new Map()

// You can still use strings to define routes
routes.set('/', Home)
routes.set('/hello/:first/:last?', Name)

// The keys for the next routes are regular expressions
// You will very likely always want to start the regular expression with ^
routes.set(/^\/hola\/(.*)/i, Name)
routes.set(/^\/buongiorno(\/([a-z]+))/i, Name)

// Catch-all, must be last
routes.set('*', NotFound)

When you define routes as regular expressions, the params prop is populated with an array with the result of the matches from the regular expression.

For example, with this Name.svelte route:

<p>Params is: <code>{JSON.stringify(params)}</code></p>
<script>
// You need to define the component prop "params"
export let params = {}
</script>

When visiting #/hola/amigos, the params prop will be ["/hola/amigos","amigos"].

This is consistent with the results of RegExp.prototype.exec().

When defining a route using a regular expression, you can optionally use named capturing groups. When using those, in addition to finding your matches in the params prop, you can find the matches for named capturing groups in params.group.
For example, consider the route:

routes.set(/^\/book\/(?<title>[a-z]+)$/, Book)

When visiting /#/book/mytitle, the params prop will be an array with ["/book/mytitle", "mytitle"], and params.groups will be a dictionary with {"title": "mytitle"}.

Advanced usage

Check out the Advanced Usage documentation for using:

svelte-spa-router's People

Contributors

almaclaine avatar byr-gdp avatar carbogninalberto avatar catchabus avatar cianx avatar collardeau avatar frederikhors avatar gdamjan avatar hmmhmmhm avatar iksflow avatar italypaleale avatar jan-kruse avatar laat avatar liquiddandruff avatar moheng233 avatar nathaniel-daniel avatar notiv-nt avatar omerman avatar pascalgermain avatar pierbover avatar primos63 avatar sisou avatar torstendittmann avatar vigenere23 avatar zedshaw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

svelte-spa-router's Issues

[Bug] Hashchange happens when route guard fails

WHEN clicking a link such as this: <a href='/' use:link>Home</a> from any route other than /
IF there is a route guard
AND the route guard fails
THEN the hashchange event fires
CAUSING any transitions on the current page to fire again as the page is re-rendered

EXPECTED The hashchange event does not fire
AND transitions are not re-triggered as the page is not re-rendered

Links to anchors within a page

Hello,

I would like to use links to go on specific locations on my page, for example a "back to the top" button. But it is always redirecting me to the homepage.
How could i use such links without the router does anything ?

Thank you

Can't SSR render SPA for SEO?

I understand that this module uses CSR(Client Side Render),
However, search engines in some countries still cannot analyze CSR pages.

Of course, for of the SEO(Search Engine Optimization),
It might be better to use the Sapper.
SSR does not easily fit the structure of Cordova or Electron.

In my opinion, if there is a separate module that renders SPA,
it will be more convenient for all platforms to be compatible at once than Sapper.

Have anyone ever configured an SEO server for this module?
I want to create a separate server module or template for performing SSRs for SPA.
I'd like to know if someone have any ideas for a better direction.

active doesn't work on anchor container

My design requires I have markup like this:

<li class="active"><a href="/pricing">Pricing</a></li>

However this doesnt' seem to work with use:active

<li use:active={'/pricing', 'active'}><a href="/pricing" use:link>Pricing</a></li>

The class actuall shows up correctly, but I'm not seeing my css changes for .active.

I tried global and also in component style tag.

//_colors.css

root {
    --main-nav-link-active: #212226;
    --main-nav-link-active-border: #1D3D30;
}


:root(li.active) {
    background-color: var(--main-nav-link-active);
    border-color: var(--main-nav-link-active-border);
}

and in the component:

    li.active {
        border-width: 1px;
        border-style: solid;
        
    }

Should browser click on back or forward execute scripts?

I know it is the old browser bfache issue, but should the router detect the back or forward click and execute the scripts again?

On each svelte-file i could do it manually with window.onpageshow = function(e) {... (but much boilerplate code)

use:link behaviour

I'm finding that the use:link doesn't appear to behave as expected in all cases:

<a href="/book/321" use:link>The Little Prince</a>

Works correctly and points to #/book/321

<a href="/book/{321}" use:link>The Little Prince</a>

Works correctly and points to #/book/321

<a href="/book/{book.id}" use:link>The Little Prince</a>

Works incorrectly and points to /book/321 - it is missing the hash in front

Able to use with base url?

We need to specific a base server side route like /admin that will serve up a SPA. We can add a base tag to the page to prefix urls but this breaks the route.

<base href="/admin" target="_blank">

the url ends up as /admin/#/settings but the router is matching /#/settings.

Is there a way to get it to work with base tag?

Named argument not working. What am I doing wrong?

I know it's probably some newbie mistake of mine, but I'm struggling with named arguments for the last hour without success, so I turned here in search for help, or sanity!

I have a simple SPA.

Defined routes in "routes.js" file like this:

import Obra from './Obra.svelte'
import ListaObras from './ListaObras.svelte'
import NotFound from './404.svelte'

const routes = {
    '/obras': ListaObras,
    '/obra/:codigo': Obra,
    '*': NotFound,
}

export { routes }

App.svelte looks like this:

<script>
  import Router from "svelte-spa-router";
  import { routes } from "./routes";
</script>

<Router {routes} />

When I navigate to http://localhost:5000/#/obras/ it works just fine, so the router is working.

When I click on any link or navigate to any address like http://localhost:5000/#/obra/144 it just shows me a 404, and it's not the NotFound one, it's a 404 from the server, meaning it's out of the app and the server is looking for some find and really didn't find the file...
But it shouldn't be looking for any file... the router should handle the URL, shouldn't it?

I'm missing something for sure, but I just can't figure out what...

Maybe a bug with a modal

I don't know if this problem is with svelte-spa-router or with sveltestrap.

Reproduction here: https://codesandbox.io/s/modern-cache-znqt3

But codesandbox is not the best place for routing issues.
You can download project: "File > Export to Zip".

The problem

Reproduction steps:

  1. Click on "Hello" menu item
  2. Click on button
  3. Modal should appear
  4. Click on the back button of the browser
  5. The problem is "Hello Home" and button are still there (on codesandbox the modal too).

Using {prefix} incredibly fix the issue but I don't know why and I suspect a bug.

image

[Feature Request] Object based route guards

Hey there, I've started using your router and I like it a lot, however I really dislike the way route guards are handled.

Taking inspiration from how https://github.com/jorgegorka/svelte-router declares the route guards, I came up with the following helper for the router.

Obviously in my actual app this function is hidden away in the utils file along with the replace and wrap imports.

<script>
    import Router, {wrap, replace} from 'svelte-spa-router'
    import Home from '../pages/home.svelte'
    import Login from '../pages/login.svelte'
    import NotFound from '../pages/not-found.svelte'
    import {isLoggedIn} from '../utils'

    const getRouterConfig = routesObj => {
        const routes = {}
        const redirects = []

        Object.entries(routesObj).forEach(([
            route,
            options
        ]) => {
            if (Object.prototype.toString.call(options) === '[object Object]') {
                if (options.guards) {
                    routes[route] = wrap(options.component, ...options.guards)

                    redirects.push({
                        route,
                        redirect: options.redirect
                    })
                }
            }

            else {
                routes[route] = options
            }
        })

        const conditionsFailed = event => {
            replace(redirects.find(redirect => redirect.route === event.detail.location).redirect)
        }

        return {
            routes,
            conditionsFailed
        }
    }

    const {
        conditionsFailed,
        routes
    } = getRouterConfig({
        '/': {
            component: Home,
            guards: [isLoggedIn],
            redirect: '/login'
        },
        '/login': Login,
        '*': NotFound
    })
</script>

<Router routes={routes} on:conditionsFailed={conditionsFailed} />

This works just fine for allowing a single object to declare the route guards and the redirect, however it still falls short as the conditionsFailed doesn't seem to provide a way to let it know which guard failed.

Any updates to make this router more declarative (E.g. My Proposed Router Config Object) would be great, though my helper function is allowing that at the moment so it's not a big issue

However getting some more info as to why the conditionsFailed method was called beyond just the route that was attempted to be navigated to would be great!

Breaks in the Webpack (svelte-loader)

Good evening, guys!

Error

ERROR in ./node_modules/svelte-spa-router/Router.svelte 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

> <script context="module">
| // Something's wrong with eslint on this file
| /* eslint-disable no-multiple-empty-lines */
 @ ./src/product1/App.svelte 12:0-39
 @ ./src/product1/index.js

Svelte file

<script>
  //  App.svelte
  import Router from 'svelte-spa-router'
</script>

<h1>Hello</h1>

Webpack config

const dotenv = require('dotenv')
const { resolve } = require('path')

dotenv.config()


module.exports = {
  // Mode
  mode: process.env.MODE || 'production',

  // Modules
  module: {
    rules: [
      // Svelte
      {
        test: /\.(html|svelte)$/,
        exclude: /node_modules/,
        use: {
          loader: 'svelte-loader',
          options: { /* Options and preprocess */ }
        }
      }
    ]
  },

  // Resolve
  resolve: {
    extensions: ['.mjs', '.js', '.svelte'],
    mainFields: ['svelte', 'browser', 'module', 'main'],
    alias: {
      svelte: resolve('node_modules', 'svelte'),
    }
  },

  // Entry
  entry: {
    product1: resolve('src/product1/index.js'),
    product2: resolve('src/product2/index.js')
  },

  // Output
  output: {
    filename: '[name].js', // Saída: product1.js, product2.js
    path: resolve('dist/')
  },
}

Packages

{
  "name": "svelte-spa-router-with-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js --progress"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "dotenv": "^8.2.0",
    "svelte": "^3.13.0",
    "svelte-loader": "^2.13.6",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10"
  },
  "dependencies": {
    "svelte-spa-router": "^1.3.0"
  }
}

Router features

A complete router needs features like Lazying loading, route groups, Router hooks for authorizations, showing progress bar, Nested routes/layout and route animations like https://github.com/alshdavid/crayon. Is it possible to add these features?

Layouts

Will you or do you have any plans to add a layout to the Nested routes feature?

Named Params not working as expected

I am trying to get the named params to work, I have a set of routes:

const routes = [
  { name: "/project/:projectName/details", component: ProjectDetails }
];

I am trying to keep the routs as simple as possible. What I would expect is that the URL:

http://localhost:5000/project/Something/details

What I get as the currentRoute is:

{
    "name": "/project/details",
    "component": "",
    "queryParams": {},
    "namedParams": {
        "projectName": "details"
    },
    "path": "/project/Something/details"
}

What I would expect is:

{
    "name": "/project/:projectName/details",
    "component": "",
    "queryParams": {},
    "namedParams": {
        "projectName": "Something"
    },
    "path": "/project/Something/details"
}

Am I doing something wrong? If needed I can try and create an example to reproduce.

when using "use:link" middle click will not work

When using the "use:link" option and i want to open the link in a new tap (middle click in the mouse) it will open as /book/321 instead of /#/book/321.

<a href="/book/321" use:link>The Little Prince</a>

svelte version 3.13.0 breaks use:active

Just updated from svelte version 3.12.1 to 3.13.0.

The node in active.js@active(node, path, className)
https://github.com/ItalyPaleAle/svelte-spa-router/blob/master/active.js#L37

is not present anymore.

When I don't give use:active a custom css class, everything works fine.

Example using use:active={'/'}:

node: [object HTMLLIElement]
path: /
className:undefined

Example using use:active={'/', 'active'}:

node: [object HTMLLIElement]
path: active
className:undefined

Route if routes are set

If set routes later and if no routing happend, the router should route with the new routes.
Currently we can not replace or push the same current route.

<script>
  import Router from 'svelte-spa-router'
  import SomeView from 'some-view.svelte'

  let routes

  // async fake
  setTimeout( () => {
    routes = {
       '/': SomeView
    }
    // push or replace here will not work
  }, 1000)
</script>

<Router {routes}></Router>

Or is it possible to call an init function manually, after setting routes?

According to #53 i want to set the routes after settings are loaded and user-login is verified.

"routeLoaded" just for some nested routes, not for all

@ItalyPaleAle, I agree with you about the few (amazing) basic features that make this router great.

For the window.scrollTo(0, 0) problem (#49) your hint to use the new routeLoaded function is great and I'm using it right now like this:

function routeLoaded () {
  window.scrollTo(0, 0);
}

But it works for every underlying route.

And maybe this is a new problem now.

How to use it only for some <Router> (parent) components?

devDependencies

Mind if we update the docs to utilize devDependencies instead of dependencies? I'll be happy to do a PR for it but it seems so small I'm not sure you need one for it.

It's one of the things I am so drawn to Svelte about, the devDependencies of it all!

Change routes file, app reloads, can't access changes?

Hey there,

I've installed this package with npm and imported it in my App.svelte file. Created a src/routes.js as in the example, with the following config:

import Dashboard from './components/pages/Dashboard.svelte'
import Register from './components/pages/Register.svelte'
import Login from './components/pages/Login.svelte'

const routes = {
	'/': Dashboard,
	'/register': Register,
	'/login': Login
};

export default routes;

Import the file in my App.svelte file, everything works! I then edited src/routes.js to be like the following:

import Dashboard from './components/pages/Dashboard.svelte'
import Register from './components/pages/Register.svelte'
import Login from './components/pages/Login.svelte'
import CreateNewOrganization from './components/pages/CreateNewOrganization.svelte'

const routes = {
	'/': Dashboard,
	'/create_organization': CreateNewOrganization,
	'/register': Register,
	'/login': Login
};

export default routes;

No compiler errors for the new component, but it won't show the new route! Tried restart npm run dev and still nothing. I also tried editing an existing route as follows:

import Dashboard from './components/pages/Dashboard.svelte'
import Register from './components/pages/Register.svelte'
import Login from './components/pages/Login.svelte'

const routes = {
	'/': Dashboard,
	'/register2': Register,
	'/login': Login
};

export default routes;

and still, I have to navigate to /regsiter to get to the registration component.

I tried clearling my cookies/localstorage just for kicks, no change in behaviour.

Any ideas?

Change window title when navigating

Tried to add these on main svelte code App.svelte

$: console.log(window.document.title = 'Svelte: ' + $location);

but this didn't work, any way to get callback when navigating/url changed?

the purpose is labeling the history, so it won't show exactly the same title:
image

Injecting props into components

Hello, is there a way to injects props into components being routed to? Some of my components depend on props to be mounted with. I know I could pass simple things as params in a route, but some of the props I use are web workers or complex objects. How would I go about injecting them into components mounted upon navigating to a route? Thanks!

use:active breaks when removing a link from an #each

use:active breaks when removing a link from an #each array.

My example:

{#each testData as testItem}
    <li use:active={testItem.link, 'active' }>
        <a href={testItem.link} use:link>{testItem.name}</a>
        - 
        <i on:click={() => removeData(testItem.id)}>X</i>
    </li>
{/each}

let testData = [
    {
        id: 1,
        name: "link",
        link: "/hello/1"
    },
    {
        id: 2,
        name: "link2",
        link: "/hello/2"
    },
    {
        id: 3,
        name: "link3",
        link: "/hello/3"
    },
];

function removeData(id) {
    testData = testData.filter(data => data.id != id);
}

After a link in the array is removed, the active state remains frozen on the current one and does not change anymore.

Conversely, when a link is added, everything still works. Only if a link with a use:active is removed.

Right now I am looking into the problem...

I forked the example from this project and added code where the error occurs:
https://github.com/TorstenDittmann/svelte-spa-router/tree/master/example

Routes open at bottom of page

Not sure if this is a bug or I implemented something wrong:

When I click on a link, it opens the new route wherever the original view was. You can recreate this on my website: at cabreraalex.com, scroll to the bottom and click on the title "Discovery of Intersectional Bias in Machine Learning Using Automatic Subgroup Generation". The new page route opens at the bottom.

Thanks!

Nested routes click to parent problem

Example:

npx degit sveltejs/template nested-routes
cd nested-routes
npm install svelte-spa-router
npm install
npm run dev

App.svelte

<script>
  import Router from 'svelte-spa-router'
  import CallView from './CallView.svelte'

  const routes = {
    '/call/*': CallView
  }
</script>
<Router {routes}></Router>

CallView.svelte

<script>
  import Router, { push } from 'svelte-spa-router'
  import CallEditView from './CallEditView.svelte'

  const routes = {
    '/call/edit/:id': CallEditView
  }

  // this is only called one time, when the page load, but not wenn you push the button
  // (onMount / async stuff fake)
  setTimeout( () => {
    push('/call/edit/1')
  }, 1000)
</script>

<button on:click={() => push('/call/') }>Go to /call/</button><br>

<Router {routes}></Router>

CallEditView.svelte

<script>
  export let params = {}
</script>

Edit {params.id}

Open http://localhost:5000/#/call/
after 1 second CallView pushes to nested view /call/edit/1
click the button,
no nested view is visible, cause setTimeout/onMount is only called once.

Reloading a route with different params

Suppose the route #/widget/:param goes to the component Widget.
I believe that if you are at #/widget/paramA and you route to #/widget/paramB, the router does not destroy and re-load the component Widget, but rather just changes the values of params within the existing component.

I do not know if this is the intended behavior or not, but it means that onMount is not called again, which for some components is a critical part of their use. It would be nice to at least have the built-in option to destroy the old component and create a new one in this case, so that onMount runs as expected.

are query params supported?

Is there support for parsing query params like

127.0.0.1:5000/#?q=query&page=1&len=20

For example I'd like the following route to match /wild and be able to read the params

http://127.0.0.1:5000/#/wild?q=query&page=10

event to indicate new route loading

Is it possible to have another event that gets emitted when navigation to a new route starts . this will make it easy to use modules like progress bar (progressbar.start in onrouteLoading event and progressbar.stop in onRouteLoaded or conditionsFailed events)

?options=on#

Hello, I don't know if it has to do With the router, Svelte or the browser itself but a few days ago I started getting my url start with ...domain...?options=on#/... It is not spontaneous, it's when I do certain actions in a site, It reloads with the url like that. Any clues as to why It may be happening?

FYI: I Chose no to include snippets because I don't know where to look and the project is quite big by now, If you'd like to take a look at something specific just ask, otherwise I could invite you to the repo.

Thank you very much for your help and I guess I crack open a Pale Ale in the mean time ;)

use dynamic routes with wrap

@ItalyPaleAle you said in your PR for Wrap that you would make modifications to take a promise as first parameter for wrap to enable dynamic wrap import. Is this possible now. this seems like the only way to use both features (wrap and preconditions alongside dynamic route loading )

refreshing page always goes to /login

Clicking around works as expected but if i'm on say /pricing page and refresh i get the /login page for some reason.

import Home from './components/Home.svelte';
import Login from './components/Login.svelte';
import Signup from './components/Signup.svelte';
import Pricing from './components/Pricing.svelte';
import Forgot from './components/Forgot.svelte';
import Connections from './components/Connections.svelte';

const routes = {
    '/': Home,
    '/pricing': Pricing,
    '/login': Login,
    '/signup': Signup,
    '/forgot': Forgot,
    '/connections': Connections,
};

export default routes;

Navigation guards

Hello,
Is it possible to call an action before each route change like vue-router ?
Would be great if we could do something like that:

...
import router from 'svelte-spa-router'

router.beforeEach(() => {
  if(!$admin)){
    push('/forbidden')
  }
})
...

Actually i'm doing this kind of user verfication in all my components ! :S
Thank you a lot!

Feature request: onBefore

When click a link and before route to the target, it would be nice to have a global onBefore function or a onBefore on each route (or both).

E.g. if a page has modified fields, you can ask in a modal to save before leaving the page.

Currently you must implement it on your own on each link

<script>
function beforeSwitch() {
  if (isModified && alert('Leave?')) {
    const url = new URL(this.href)
    push(url.hash.substr(1)) // remove # and route to target
  }
}
</script>

<a href="#/target" on:click|preventDefault={beforeSwitch}>Link</a>

code splitting of routers?

I have recently focused on developing the Svelte project using Parcel.... By the way, I wonder if the code splitting of parcel can be applied to the routes index map of spa router.

import Home from './routes/Home.svelte'
import Book from './routes/Book.svelte'
import NotFound from './routes/NotFound.svelte'

const routes = {
    '/': Home,

    // Might be like this?
    '/author/:first/:last?': async () => await import('./routes/Author.svelte'),

    '/book/*': Book,
    '*': NotFound
}

Reset scroll position when navigating to a new page

First of all, thanks for this awesome static route lib :)

I'm wondering if there's a cleaner way than window.scrollTo(0,0) when navigating to a new page from a page that has been scrolled even a tiny bit down? Since if I don't do this, the new page will be shown with the previous scroll position.

Am I missing anything or is this the right way to do it? Cheers.

edit: Did a quick search but forgot to remove filters. Seems like others are also doing the scroll resetting manually #26

Open modal with dedicated route (URL)

Something that I can't find and that is still missing today is a mechanism to open modals with a dedicated URL.

Example:

const routes = {
  '/players': PlayersList,
  '/players/:playerID/edit': PlayersList
}
  • I'm on listing page (URL: /#/players)
  • I click on a row with ID 4
  • the function handleRowClick() in PlayersList component:
    • push a new URL: /#/players/4/edit
    • check for $location:
      • if /edit is present: open modal
      • if not: return

Does this make sense?

Just one problem with this today. Using this function:

async function handleRowClick (playerID) {
  await push(`/players/${playerID}/edit`)
  await console.log('$location is:', $location) // not the new one, still the old one: /players
  await tick()
  await checkLocation()
}

async function checkLocation () {
  await tick()
  await console.log('$location is:', $location) // not the new one, still the old one: /players
}

If I click the same row or another one (different ID) the $location now is the old (not anymore new) one (/players/4/edit).

How is it possible?

custom DOM events won't bubble past <Route />

Having tried the first proposed work-around of #42, my findings are very strange.
With HTML like this:

<div bind:this={listener} class="width-adjuster mt-3">
  <Index />
</div>

and custom event being fired by a component deep inside Index:

new CustomEvent('purchase', { bubbles: true, cancelable: false, detail: prjCode }) 

Everything works fine - listener gets the event. Once I replace <Index /> by <Route />, everything
but the event delivery works fine, which I absolutely don't understand, because the DOM structure
looks exactly the same. Any ideas and possible explanations are very welcome. Thanks!

Just one wildcard route for nested routes?

It would be amazing to have just one route declaration for this case.

As it's now:

routes = {
  '/books': Books,
  '/books/*': Books,

  '*': NotFound,
}

It would be amazing this:

routes = {
  '/books*': Books,

  '*': NotFound,
}

In Books component I have these routes:

routes = {
  '/books': Index,
  '/new': NewBook,
  '/show': ShowBook,
}

Now it doesn't work, right?

Is it possible? Am I wrong and this is not good?

Route pre-conditions promises

Pro-Conditions should resolve promises.

Before a route is called, i load settings async from a local storage and check user token async on a server before i route the user to the target page.
In the meantime i show a "Please wait" screen.

Pre-condition (guard) for multiple routes

@ItalyPaleAle your work is amazing! Thanks a lot.

I have read all #40 and #23: an outstanding job!

Just one question: can I avoid repeating the same wrap() pre-condition (guard) for each route?

Example:

TODAY:

import {replace} from 'svelte-spa-router'
const routes = {
  '/login': LoginForm,

  '/': wrap(Home,
    () => {
      if (!isAuthenticated) {
        return replace('/login')
      }
      return true
    }
  ),

  '/players*': wrap(Players,
    () => {
      if (!isAuthenticated) {
        return replace('/login')
      }
      return true
    }
  ),

  '/teams': wrap(Teams,
    () => {
      if (!isAuthenticated) {
        return replace('/login')
      }
      return true
    }
  ),

  '*': wrap(NotFoundPage,
    () => {
      if (!isAuthenticated) {
        return replace('/login')
      }
      return true
    }
  )
}

WHAT I NEED:

import {replace} from 'svelte-spa-router'
// Something like this:
const routes = {
  '/login': LoginForm,

  [
    '/',
    '/players*',
    '/teams',
    '*'
  ]: wrap(SOME_MAGIC,
    () => {
      if (!isAuthenticated) {
        return replace('/login')
      }
      return true
    }
  ),
}

Something like a Protected component to protect all routes.

Is there a way?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.