diff --git a/Advanced Usage.md b/Advanced Usage.md index 3fddf5a..1a81800 100644 --- a/Advanced Usage.md +++ b/Advanced Usage.md @@ -8,10 +8,10 @@ Thanks to the many features of Svelte 3 or other components in the ecosystem, sv - [Dynamically-imported routes and placeholders](#async-routes-and-loading-placeholders) - [Route pre-conditions](#route-pre-conditions) ("route guards") - [Adding user data to routes](#user-data) + - [Static props](#static-props) - [`routeEvent` event](#routeevent-event) - [`routeLoading` and `routeLoaded` events](#routeloading-and-routeloaded-events) - [Querystring parsing](#querystring-parsing) -- [Static props](#static-props) - [Route transitions](#route-transitions) - [Nested routers](#nested-routers) - [Route groups](#route-groups) @@ -26,6 +26,7 @@ The `wrap` method allows a few more interesting features, however: - In addition to dynamically-importing components, you can define a component to be shown while a dynamically-imported one is being requested - You can add pre-conditions to routes (sometimes called "route guards") - You can add custom user data that is then used with the [`routeLoading` and `routeLoaded` events](#routeloading-and-routeloaded-events) +- You can set static props, which are passed to the component as mounted by the router ### The `wrap` method @@ -42,7 +43,8 @@ It accepts a single `options` argument that is an object with the following prop - `options.loadingComponent`: Used together with `asyncComponent`, this is a Svelte component, that must be part of the bundle, which is displayed while `asyncComponent` is being downloaded. If this is empty, then the router will not display any component while the request is in progress. - `options.loadingParams`: When using a `loadingComponent`, this is an optional dictionary that will be passed to the component as the `params` prop. - `options.userData`: Optional dictionary that will be passed to events such as `routeLoading`, `routeLoaded`, `conditionsFailed`. -- `options.conditions`: Array of route pre-condition functions to add, which will be executed in order. +- `options.conditions`: Optional array of route pre-condition functions to add, which will be executed in order. +- `options.props`: Optional dictionary of props that are passed to the component when mounted. The props are expanded with the spread operator (`{...props}`), so the key of each element becomes the name of the prop. One and only one of `options.component` or `options.asyncComponent` must be set; all other properties are optional. @@ -269,6 +271,43 @@ function routeLoaded(event) { ```` +### Static props + +In certain cases, you might need to pass static props to a component within the router. + +For example, assume this component `Foo.svelte`: + +```svelte +

The secret number is {num}

+ +``` + +If `Foo` is a route in your application, you can pass a series of props to it through the router, using `wrap`: + +```svelte + + +``` + ## `routeEvent` event The custom `routeEvent` event can be used to bubble events from a component displayed by the router, to the router's parent component. @@ -427,41 +466,6 @@ With the same URL as before, the result would be: qs supports advanced things such as arrays, nested objects, etc. Check out their [README](https://github.com/ljharb/qs) for more information. -## Static props - -In certain cases, you might need to pass static props to a component within the router. - -For example, assume this component `Foo.svelte`: - -```svelte -

The secret number is {num}

- -``` - -If `Foo` is a route in your application, you can pass a series of props to it through the router, with: - -```svelte - - -``` - ## Route transitions It's easy to add a nice transition between routes, leveraging the built-in [transitions](https://svelte.dev/docs#Transitions) of Svelte 3. diff --git a/README.md b/README.md index 29635d1..3a43b6a 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ const routes = { } ```` -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 custom user data, adding pre-conditions (route guards), etc. +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](/Advanced%20Usage.md#route-wrapping). @@ -369,6 +369,7 @@ Check out the [Advanced Usage](/Advanced%20Usage.md) documentation for using: - [Dynamically-imported routes and placeholders](/Advanced%20Usage.md#async-routes-and-loading-placeholders) - [Route pre-conditions](/Advanced%20Usage.md#route-pre-conditions) ("route guards") - [Adding user data to routes](/Advanced%20Usage.md#user-data) + - [Static props](/Advanced%20Usage.md#static-props) - [`routeEvent` event](/Advanced%20Usage.md#routeevent-event) - [`routeLoading` and `routeLoaded` events](/Advanced%20Usage.md#routeloading-and-routeloaded-events) - [Querystring parsing](/Advanced%20Usage.md#querystring-parsing) diff --git a/Router.svelte b/Router.svelte index 27184a2..4dc8351 100644 --- a/Router.svelte +++ b/Router.svelte @@ -246,11 +246,6 @@ export let prefix = '' */ export let restoreScrollState = false -/** - * Optional props that are passed to each component in the router, expanded (with `{...props}`) - */ -export let props = {} - /** * Container for a route: path, component */ @@ -259,7 +254,7 @@ class RouteItem { * Initializes the object and creates a regular expression from the path, using regexparam. * * @param {string} path - Path to the route (must start with '/' or '*') - * @param {SvelteComponent} component - Svelte component for the route + * @param {SvelteComponent|WrappedComponent} component - Svelte component for the route, optionally wrapped */ constructor(path, component) { if (!component || (typeof component != 'function' && (typeof component != 'object' || component._sveltesparouter !== true))) { @@ -283,11 +278,13 @@ class RouteItem { this.component = component.component this.conditions = component.conditions || [] this.userData = component.userData + this.props = component.props || {} } else { // Convert the component to a function that returns a Promise, to normalize it this.component = () => Promise.resolve(component) this.conditions = [] + this.props = {} } this._pattern = pattern @@ -388,6 +385,7 @@ else { // Props for the component to render let component = null let componentParams = null +let props = {} // Event dispatcher from Svelte const dispatch = createEventDispatcher() @@ -467,6 +465,7 @@ loc.subscribe(async (newLoc) => { if (obj.loading) { component = obj.loading componentParams = obj.loadingParams + props = {} // Trigger the routeLoaded event for the loading component // Create a copy of detail so we don't modify the object for the dynamic route (and the dynamic route doesn't modify our object too) @@ -504,6 +503,9 @@ loc.subscribe(async (newLoc) => { componentParams = null } + // Set static props, if any + props = routesList[i].props + dispatchNextTick('routeLoaded', detail) break } diff --git a/test/app/src/App.svelte b/test/app/src/App.svelte index 9435399..b4bef74 100644 --- a/test/app/src/App.svelte +++ b/test/app/src/App.svelte @@ -26,26 +26,14 @@

-{#if staticProps} - -{:else} - -{/if} +

Dynamic links

@@ -127,10 +115,6 @@ function routeEvent(event) { const urlParams = new URLSearchParams(window.location.search) const restoreScrollState = !!urlParams.has('scroll') -// Check if we need to set static props, which are enabled by setting the "props=1" querystring parameter -// We're checking this for the tests, but in your code you will likely want to set this value manually -const staticProps = urlParams.has('props') ? {staticProp: 'foo'} : null - // List of dynamic links let dynamicLinks = [ { diff --git a/test/app/src/routes.js b/test/app/src/routes.js index 9c6ce44..4e2c816 100644 --- a/test/app/src/routes.js +++ b/test/app/src/routes.js @@ -10,6 +10,7 @@ import Home from './routes/Home.svelte' import Name from './routes/Name.svelte' import Loading from './routes/Loading.svelte' import Wild from './routes/Wild.svelte' +import Foo from './routes/Foo.svelte' import Regex from './routes/Regex.svelte' import NotFound from './routes/NotFound.svelte' @@ -96,6 +97,12 @@ if (!urlParams.has('routemap')) { // The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail '/lucky': wrappedLuckyRoute, + // This route has a static prop that is passed to it + '/foo': wrap({ + component: Foo, + props: {staticProp: 'this is static'} + }), + // This component contains a nested router // Note that we must match both '/nested' and '/nested/*' for the nested router to work (or look below at doing this with a Map and a regular expression) '/nested': wrap({ @@ -134,6 +141,12 @@ else { // The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail routes.set('/lucky', wrappedLuckyRoute) + // This route has a static prop that is passed to it + routes.set('/foo', wrap({ + component: Foo, + props: {staticProp: 'this is static'} + })) + // Regular expressions routes.set(/^\/regex\/(.*)?/i, Regex) routes.set(/^\/(pattern|match)(\/[a-z0-9]+)?/i, Regex) diff --git a/test/app/src/routes/Foo.svelte b/test/app/src/routes/Foo.svelte new file mode 100644 index 0000000..16a186b --- /dev/null +++ b/test/app/src/routes/Foo.svelte @@ -0,0 +1,10 @@ +{#if staticProp} +

We have a static prop: {staticProp}

+{:else} +

No static props here!

+{/if} + + \ No newline at end of file diff --git a/test/app/src/routes/Home.svelte b/test/app/src/routes/Home.svelte index a014fc5..a28e6d8 100644 --- a/test/app/src/routes/Home.svelte +++ b/test/app/src/routes/Home.svelte @@ -9,12 +9,3 @@
  • Manually change the URL's fragment/hash
  • Try refreshing the page
  • - -{#if staticProp} -

    We have a static prop: {staticProp}

    -{/if} - - diff --git a/test/cases/01-routing.test.js b/test/cases/01-routing.test.js index 6dafa97..8a458af 100644 --- a/test/cases/01-routing.test.js +++ b/test/cases/01-routing.test.js @@ -370,9 +370,9 @@ describe(' component', function() { it('static props', (browser) => { browser - .url(browser.launchUrl + '?props=1') + .url(browser.launchUrl + '/#/foo') .waitForElementVisible('#staticprop') - .expect.element('#staticprop').text.to.equal('foo') + .expect.element('#staticprop').text.to.equal('this is static') browser.end() }) diff --git a/wrap.js b/wrap.js index e031ae4..4741a8b 100644 --- a/wrap.js +++ b/wrap.js @@ -1,5 +1,10 @@ /** * @typedef {Object} WrappedComponent + * @property {SvelteComponent} component - Component to load (this is always asynchronous) + * @property {RoutePrecondition[]} [conditions] - Route pre-conditions to validate + * @property {Object} [props] - Optional dictionary of static props + * @property {Object} [userData] - Optional user data dictionary + * @property {bool} _sveltesparouter - Internal flag; always set to true */ /** @@ -15,6 +20,7 @@ * @property {SvelteComponent} [loadingComponent] - Svelte component to be displayed while the async route is loading (as a placeholder); when unset or false-y, no component is shown while component * @property {Object} [loadingParams] - Optional dictionary passed to the `loadingComponent` component as params (for an exported prop called `params`) * @property {Object} [userData] - Optional object that will be passed to events such as `routeLoading`, `routeLoaded`, `conditionsFailed` + * @property {Object} [props] - Optional key-value dictionary of static props that will be passed to the component. The props are expanded with {...props}, so the key in the dictionary becomes the name of the prop. * @property {RoutePrecondition[]|RoutePrecondition} [conditions] - Route pre-conditions to add, which will be executed in order */ @@ -22,7 +28,8 @@ * Wraps a component to enable multiple capabilities: * 1. Using dynamically-imported component, with (e.g. `{asyncComponent: () => import('Foo.svelte')}`), which also allows bundlers to do code-splitting. * 2. Adding route pre-conditions (e.g. `{conditions: [...]}`) - * 3. Adding custom userData, which is passed to route events (e.g. route loaded events) or to route pre-conditions (e.g. `{userData: {foo: 'bar}}`) + * 3. Adding static props that are passed to the component + * 4. Adding custom userData, which is passed to route events (e.g. route loaded events) or to route pre-conditions (e.g. `{userData: {foo: 'bar}}`) * * @param {WrapOptions} args - Arguments object * @returns {WrappedComponent} Wrapped component @@ -71,6 +78,7 @@ export function wrap(args) { component: args.asyncComponent, userData: args.userData, conditions: (args.conditions && args.conditions.length) ? args.conditions : undefined, + props: (args.props && Object.keys(args.props).length) ? args.props : {}, _sveltesparouter: true }