diff --git a/Advanced Usage.md b/Advanced Usage.md index 822ef78..3fddf5a 100644 --- a/Advanced Usage.md +++ b/Advanced Usage.md @@ -37,14 +37,14 @@ import Router from 'svelte-spa-router' It accepts a single `options` argument that is an object with the following properties: -- `options.route`: Svelte component to use, statically-included in the bundle. This is a Svelte component, such as `route: Foo`, with that previously imported with `import Foo from './Foo.svelte'`. -- `options.asyncRoute`: Used to dynamically-import route. This must be a function definition that returns a dynamically-imported component, such as: `asyncRoute: () => import('./Foo.svelte')` -- `options.loadingRoute`: Used together with `asyncRoute`, this is a Svelte component, that must be part of the bundle, which is displayed while `asyncRoute` 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 `loadingRoute`, this is an optional dictionary that will be passed to the component as the `params` prop. +- `options.component`: Svelte component to use, statically-included in the bundle. This is a Svelte component, such as `component: Foo`, with that previously imported with `import Foo from './Foo.svelte'`. +- `options.asyncComponent`: Used to dynamically-import components. This must be a function definition that returns a dynamically-imported component, such as: `asyncComponent: () => import('./Foo.svelte')` +- `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. -One and only one of `options.route` or `options.asyncRoute` must be set; all other properties are optional. +One and only one of `options.component` or `options.asyncComponent` must be set; all other properties are optional. You use the `wrap` method in your route definition, such as: @@ -54,7 +54,7 @@ import Books from './Books.svelte' // Using a dictionary to define the route object const routes = { '/books': wrap({ - route: Books, + component: Books, userData: {foo: 'bar'} }) } @@ -62,7 +62,7 @@ const routes = { // Using a map const routes = new Map() routes.set('/books', wrap({ - route: Books, + component: Books, userData: {foo: 'bar'} })) ``` @@ -71,19 +71,19 @@ routes.set('/books', wrap({ As mentioned in the main readme, starting with version 3 the `wrap` method is used with dynamically-imported components. This allows (when the bundler supports that, such as with Rollup or Webpack) code-splitting too, so code for less-common routes can be downloaded on-demand from the server rather than shipped in the app's core bundle. -This is done by setting the `options.asyncRoute` property to a function that returns a dynamically-imported module. For example: +This is done by setting the `options.asyncComponent` property to a function that returns a dynamically-imported module. For example: ```js const routes = { '/book/:id': wrap({ - asyncRoute: () => import('./Book.svelte') + asyncComponent: () => import('./Book.svelte') }) } ``` -Note that the value of `asyncRoute` must be a function definition, such as `() => import(…)`, and **not** `import(…)` (which is a function invocation). The latter would in fact request the module right away (albeit asynchronously), rather than on-demand when needed. +Note that the value of `asyncComponent` must be a function definition, such as `() => import(…)`, and **not** `import(…)` (which is a function invocation). The latter would in fact request the module right away (albeit asynchronously), rather than on-demand when needed. -By default, while a module is being downloaded, the router does not display any component. You can however define a component (which must be statically-included in the app's bundle) to be displayed while the router is downloading a module. This is done with the `options.loadingRoute` property. Additionally, with `options.loadingParams` you can define a JavaScript object/dictionary that is passed to the loading placeholder component as the `params` prop. +By default, while a module is being downloaded, the router does not display any component. You can however define a component (which must be statically-included in the app's bundle) to be displayed while the router is downloading a module. This is done with the `options.loadingComponent` property. Additionally, with `options.loadingParams` you can define a JavaScript object/dictionary that is passed to the loading placeholder component as the `params` prop. For example, with a `Loading.svelte` component: @@ -112,9 +112,9 @@ const routes = { // Wrapping the Book component '/book/*': wrap({ // Dynamically import the Book component - asyncRoute: () => import('./Book.svelte'), + asyncComponent: () => import('./Book.svelte'), // Display the Loading component while the request for the Book component is pending - loadingRoute: Loading, + loadingComponent: Loading, // Value for `params` in the Loading component loadingParams: { message: 'secret', @@ -138,12 +138,12 @@ import Books from './Books.svelte' const routes = { // Using a statically-included component and adding user data '/books': wrap({ - route: Books, + component: Books, userData: {foo: 'bar'} }), // Same, but for dynamically-loaded components '/authors': wrap({ - asyncRoute: () => import('./Authors.svelte'), + asyncComponent: () => import('./Authors.svelte'), userData: {hello: 'world'} }) } @@ -183,7 +183,7 @@ const routes = { // This route has a pre-condition function that lets people in only 50% of times, and a second pre-condition that is always true '/lucky': wrap({ // The Svelte component used by the route - route: Lucky, + component: Lucky, // Custom data: any JavaScript object // This is optional and can be omitted @@ -224,7 +224,7 @@ const routes = { // This route has an async function as pre-condition '/admin': wrap({ // Use a dynamically-loaded component for this - asyncRoute: () => import('./Admin.svelte'), + asyncComponent: () => import('./Admin.svelte'), // Adding one pre-condition that's an async function conditions: [ async (detail) => { diff --git a/README.md b/README.md index 174ce2c..29635d1 100644 --- a/README.md +++ b/README.md @@ -127,26 +127,26 @@ 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 routes and code-splitting +### Dynamically-imported components and code-splitting -Starting with version 3.0, svelte-spa-router supports dynamically-imported routes (via the `import()` method). 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. +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 routes, you need to leverage the `wrap` method (the same one that enables support for [route pre-conditions](/Advanced%20Usage.md#route-pre-conditions)). First, import the `wrap` method: +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](/Advanced%20Usage.md#route-wrapping)). First, import the `wrap` method: ```js 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 `asyncRoute` property: +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: ```js wrap({ - asyncRoute: () => import('./Foo.svelte') + asyncComponent: () => import('./Foo.svelte') }) ``` -> Note: the value of `asyncRoute` must be the **definition of a function** returning a dynamically-imported component, such as `asyncRoute: () => import('./Foo.svelte')`. -> Do **not** use `asyncRoute: import('./Foo.svelte')`, which is a function invocation instead. +> 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: @@ -163,12 +163,12 @@ const routes = { // Wrapping the Author component '/author/:first/:last?': wrap({ - asyncRoute: () => import('./routes/Author.svelte') + asyncComponent: () => import('./routes/Author.svelte') }), // Wrapping the Book component '/book/*': wrap({ - asyncRoute: () => import('./routes/Book.svelte') + asyncComponent: () => import('./routes/Book.svelte') }), // Catch-all route last @@ -178,7 +178,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. -You can learn more about all the features of `wrap` in the documentation for [Route wrapping](/Advanced%20Usage.md#route-wrapping). +You can learn more about all the features of `wrap` in the documentation for [route wrapping](/Advanced%20Usage.md#route-wrapping). ### Navigating between pages diff --git a/Router.svelte b/Router.svelte index 5a18412..27184a2 100644 --- a/Router.svelte +++ b/Router.svelte @@ -4,20 +4,20 @@ import {tick} from 'svelte' import {wrap as _wrap} from './wrap' /** - * Wraps a route to add route pre-conditions. + * Wraps a component to add route pre-conditions. * @deprecated Use `wrap` from `svelte-spa-router/wrap` instead. This function will be removed in a later version. * - * @param {SvelteComponent} route - Svelte component for the route + * @param {SvelteComponent} component - Svelte component for the route * @param {Object} [userData] - Optional object that will be passed to each `conditionsFailed` event * @param {...function(RouteDetail): boolean} conditions - Route pre-conditions to add, which will be executed in order - * @returns {WrappedRoute} Wrapped route + * @returns {WrappedComponent} Wrapped component */ -export function wrap(route, userData, ...conditions) { +export function wrap(component, userData, ...conditions) { // Use the new wrap method and show a deprecation warning // eslint-disable-next-line no-console console.warn('Method `wrap` from `svelte-spa-router` is deprecated and will be removed in a future version. Please use `svelte-spa-router/wrap` instead. See http://bit.ly/svelte-spa-router-upgrading') return _wrap({ - route, + component, userData, conditions }) @@ -247,7 +247,7 @@ export let prefix = '' export let restoreScrollState = false /** - * Optional props that are passed to each route in the component, expanded (with `{...props}`) + * Optional props that are passed to each component in the router, expanded (with `{...props}`) */ export let props = {} @@ -280,7 +280,7 @@ class RouteItem { // Check if the component is wrapped and we have conditions if (typeof component == 'object' && component._sveltesparouter === true) { - this.component = component.route + this.component = component.component this.conditions = component.conditions || [] this.userData = component.userData } @@ -345,7 +345,7 @@ class RouteItem { /** * Dictionary with route details passed to the pre-conditions functions, as well as the `routeLoading`, `routeLoaded` and `conditionsFailed` events * @typedef {Object} RouteDetail - * @property {string|Object} route - Route matched as defined in the route definition (could be a string or a reguar expression object) + * @property {string|RegExp} route - Route matched as defined in the route definition (could be a string or a reguar expression object) * @property {string} location - Location path * @property {string} querystring - Querystring from the hash * @property {Object} [userData] - Custom data passed by the user diff --git a/UPGRADING.md b/UPGRADING.md index 31d4a71..ac46d32 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -19,7 +19,7 @@ If your application was decoding URL parameters before, remove that invocation w ### New `wrap` method -The `wrap` method exported by `svelte-spa-router` has been deprecated. Even though it remains available and functional (albeit showing a warning), it will be removed in a later version of the router. +The `wrap` method exported by `svelte-spa-router` has been deprecated. Even though it remains available and functional (albeit showing a warning in the console), it will be removed in a later version of the router. Please use the new `wrap` method exported by `svelte-spa-router/wrap` instead. This method's signature accepts a single argument which is an object of properties. It adds support for many other features too, such as dynamically-imported routes. @@ -34,7 +34,7 @@ To upgrade, maintaining the same functionality: import {wrap} from 'svelte-spa-router' const routes = { - // Method signature: wrap(route, userData, ...conditions) + // Method signature: wrap(component, userData, ...conditions) '/foo': wrap( // Component Foo, @@ -59,7 +59,7 @@ const routes = { // Method signature: wrap(options) '/foo': wrap({ // Component - route: Foo, + component: Foo, // Custom data customData: {foo: 'bar'}, // Pre-condition function diff --git a/examples/dynamic-imports/src/routes.js b/examples/dynamic-imports/src/routes.js index 95a68fe..74b596b 100644 --- a/examples/dynamic-imports/src/routes.js +++ b/examples/dynamic-imports/src/routes.js @@ -18,10 +18,10 @@ export default { // This is dynamically imported, so the code is loaded on-demand from the server '/hello/:first/:last?': wrap({ // Note that this is a function that returns the import - asyncRoute: () => import('./routes/Name.svelte'), - // Show the loading component while the route is being downloaded - loadingRoute: Loading, - // Pass values for the `params` prop of the loading route + asyncComponent: () => import('./routes/Name.svelte'), + // Show the loading component while the component is being downloaded + loadingComponent: Loading, + // Pass values for the `params` prop of the loading component loadingParams: { message: 'Loading the Name route…' } @@ -34,17 +34,17 @@ export default { // Note that this is a function that returns the import // We're adding an artificial delay of 5 seconds so you can experience the loading even on localhost // Note that normally the modules loaded with `import()` are cached, so the delay exists only on the first request. - // In this case, we're adding a delay every time the route is loaded - asyncRoute: () => import('./routes/Wild.svelte') + // In this case, we're adding a delay every time the component is loaded + asyncComponent: () => import('./routes/Wild.svelte') .then((component) => { return new Promise((resolve) => { // Wait 5 seconds before returning setTimeout(() => resolve(component), 5000) }) }), - // Show the loading component while the route is being downloaded - loadingRoute: Loading, - // Pass values for the `params` prop of the loading route + // Show the loading component while the component is being downloaded + loadingComponent: Loading, + // Pass values for the `params` prop of the loading component loadingParams: { message: 'Loading the Wild route…' } diff --git a/test/app/src/routes.js b/test/app/src/routes.js index c1488c9..9c6ce44 100644 --- a/test/app/src/routes.js +++ b/test/app/src/routes.js @@ -14,15 +14,15 @@ import Regex from './routes/Regex.svelte' import NotFound from './routes/NotFound.svelte' const wrappedLuckyRoute = wrap({ - // Add an artificial delay so we can experience the route loading - asyncRoute: () => import('./routes/Lucky.svelte') + // Add an artificial delay so we can experience the component loading + asyncComponent: () => import('./routes/Lucky.svelte') .then((res) => { return new Promise((resolve) => { setTimeout(() => resolve(res), 2000) }) }), // Component to show while the module is being downloaded - loadingRoute: Loading, + loadingComponent: Loading, // Props for the loading component loadingParams: {message: 'secret'}, // Set some user data @@ -87,7 +87,7 @@ if (!urlParams.has('routemap')) { '/wild': Wild, // Special route that has custom data that will be passed to the `routeLoaded` event '/wild/data': wrap({ - route: Wild, + component: Wild, userData: {hello: 'world'} }), '/wild/*': Wild, @@ -99,10 +99,10 @@ if (!urlParams.has('routemap')) { // 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({ - asyncRoute: () => import('./routes/Nested.svelte') + asyncComponent: () => import('./routes/Nested.svelte') }), '/nested/*': wrap({ - asyncRoute: () => import('./routes/Nested.svelte') + asyncComponent: () => import('./routes/Nested.svelte') }), // Catch-all, must be last @@ -125,7 +125,7 @@ else { routes.set('/wild', Wild) // Special route that has custom data that will be passed to the `routeLoaded` event routes.set('/wild/data', wrap({ - route: Wild, + component: Wild, userData: {hello: 'world'} })) routes.set('/wild/*', Wild) @@ -141,7 +141,7 @@ else { // This component contains a nested router // Thanks to being able to define routes via regular expressions, this allows us to use a single line rather than 2 ('/nested' and '/nested/*') routes.set(/^\/nested(\/(.*))?/, wrap({ - asyncRoute: () => import('./routes/Nested.svelte') + asyncComponent: () => import('./routes/Nested.svelte') })) // Catch-all, must be last diff --git a/wrap.js b/wrap.js index d33fb84..e031ae4 100644 --- a/wrap.js +++ b/wrap.js @@ -1,51 +1,51 @@ /** - * @typedef {Object} WrappedRoute + * @typedef {Object} WrappedComponent */ /** * @callback RoutePrecondition * @param {RouteDetail} detail - Route detail object - * @returns {boolean} If the callback returns a false-y value, it's interpreted as the precondition failed, so it aborts loading the route (and won't process other pre-condition callbacks) + * @returns {boolean} If the callback returns a false-y value, it's interpreted as the precondition failed, so it aborts loading the component (and won't process other pre-condition callbacks) */ /** * @typedef {Object} WrapOptions - * @property {SvelteComponent} [route] - Svelte component to load (this is incompatible with `asyncRoute`) - * @property {function(): Promise} [asyncRoute] - Function that returns a Promise that fulfills with a Svelte component (e.g. `{asyncRoute: () => import('Foo.svelte')}`) - * @property {SvelteComponent} [loadingRoute] - Svelte component to be displayed while the async route is loading; when unset or false-y, no route is shown while loading - * @property {Object} [loadingParams] - Optional dictionary passed to the `loadingRoute` component as params (for an exported prop called `params`) + * @property {SvelteComponent} [component] - Svelte component to load (this is incompatible with `asyncComponent`) + * @property {function(): Promise} [asyncComponent] - Function that returns a Promise that fulfills with a Svelte component (e.g. `{asyncComponent: () => import('Foo.svelte')}`) + * @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 {RoutePrecondition[]|RoutePrecondition} [conditions] - Route pre-conditions to add, which will be executed in order */ /** - * Wraps a route to enable multiple capabilities: - * 1. Using dynamically-imported routes, with (e.g. `{asyncRoute: () => import('Foo.svelte')}`), which also allows bundlers to enable code-splitting. + * 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}}`) * * @param {WrapOptions} args - Arguments object - * @returns {WrappedRoute} Wrapped route + * @returns {WrappedComponent} Wrapped component */ export function wrap(args) { if (!args) { throw Error('Parameter args is required') } - // We need to have one and only one of route and asyncRoute + // We need to have one and only one of component and asyncComponent // This does a "XNOR" - if (!args.route == !args.asyncRoute) { - throw Error('One and only one of route and asyncRoute is required') + if (!args.component == !args.asyncComponent) { + throw Error('One and only one of component and asyncComponent is required') } - // If the route is not async, wrap it into a function returning a Promise - if (args.route) { - args.asyncRoute = () => Promise.resolve(args.route) + // If the component is not async, wrap it into a function returning a Promise + if (args.component) { + args.asyncComponent = () => Promise.resolve(args.component) } - // Parameter asyncRoute and each item of conditions must be functions - if (typeof args.asyncRoute != 'function') { - throw Error('Parameter asyncRoute must be a function') + // Parameter asyncComponent and each item of conditions must be functions + if (typeof args.asyncComponent != 'function') { + throw Error('Parameter asyncComponent must be a function') } if (args.conditions) { // Ensure it's an array @@ -59,16 +59,16 @@ export function wrap(args) { } } - // Check if we have a loading route - if (args.loadingRoute) { - args.asyncRoute.loading = args.loadingRoute - args.asyncRoute.loadingParams = args.loadingParams || undefined + // Check if we have a placeholder component + if (args.loadingComponent) { + args.asyncComponent.loading = args.loadingComponent + args.asyncComponent.loadingParams = args.loadingParams || undefined } // Returns an object that contains all the functions to execute too // The _sveltesparouter flag is to confirm the object was created by this router const obj = { - route: args.asyncRoute, + component: args.asyncComponent, userData: args.userData, conditions: (args.conditions && args.conditions.length) ? args.conditions : undefined, _sveltesparouter: true