From 7531ca20edd2ae1efe1e431c48f0d4b37d3d467d Mon Sep 17 00:00:00 2001 From: Lorenzo Fiore Date: Sat, 26 Sep 2020 01:44:27 +0200 Subject: [PATCH] Add route guards --- example/public/App.js | 1095 ++++++++++++++++++++++++----------------- src/Link.svelte | 33 +- src/Route.svelte | 35 +- src/Router.svelte | 17 +- src/utils.js | 41 +- 5 files changed, 725 insertions(+), 496 deletions(-) diff --git a/example/public/App.js b/example/public/App.js index 1fe2728..abf5f8c 100644 --- a/example/public/App.js +++ b/example/public/App.js @@ -1,229 +1,351 @@ 'use strict'; -function noop() {} - function run(fn) { - return fn(); + return fn(); } - function blank_object() { - return Object.create(null); + return Object.create(null); } - function run_all(fns) { - fns.forEach(run); -} - -function safe_not_equal(a, b) { - return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); + fns.forEach(run); } let current_component; - function set_current_component(component) { - current_component = component; + current_component = component; +} +function validate_component(component, name) { + if (!component || !component.$$render) { + if (name === 'svelte:component') + name += ' this={...}'; + throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`); + } + return component; +} +let on_destroy; +function create_ssr_component(fn) { + function $$render(result, props, bindings, slots) { + const parent_component = current_component; + const $$ = { + on_destroy, + context: new Map(parent_component ? parent_component.$$.context : []), + // these will be immediately discarded + on_mount: [], + before_update: [], + after_update: [], + callbacks: blank_object() + }; + set_current_component({ $$ }); + const html = fn(result, props, bindings, slots); + set_current_component(parent_component); + return html; + } + return { + render: (props = {}, options = {}) => { + on_destroy = []; + const result = { title: '', head: '', css: new Set() }; + const html = $$render(result, props, {}, options); + run_all(on_destroy); + return { + html, + css: { + code: Array.from(result.css).map(css => css.code).join('\n'), + map: null // TODO + }, + head: result.title + result.head + }; + }, + $$render + }; } -function get_current_component() { - if (!current_component) throw new Error(`Function called outside component initialization`); - return current_component; +function noop() { } +function run$1(fn) { + return fn(); +} +function blank_object$1() { + return Object.create(null); +} +function run_all$1(fns) { + fns.forEach(run$1); +} +function is_function(thing) { + return typeof thing === 'function'; +} +function safe_not_equal(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} +function subscribe(store, ...callbacks) { + if (store == null) { + return noop; + } + const unsub = store.subscribe(...callbacks); + return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; +} +function get_store_value(store) { + let value; + subscribe(store, _ => value = _)(); + return value; +} +function custom_event(type, detail) { + const e = document.createEvent('CustomEvent'); + e.initCustomEvent(type, false, false, detail); + return e; } +let current_component$1; +function set_current_component$1(component) { + current_component$1 = component; +} +function get_current_component() { + if (!current_component$1) + throw new Error(`Function called outside component initialization`); + return current_component$1; +} function onMount(fn) { - get_current_component().$$.on_mount.push(fn); + get_current_component().$$.on_mount.push(fn); } - function onDestroy(fn) { - get_current_component().$$.on_destroy.push(fn); + get_current_component().$$.on_destroy.push(fn); +} +function createEventDispatcher() { + const component = get_current_component(); + return (type, detail) => { + const callbacks = component.$$.callbacks[type]; + if (callbacks) { + // TODO are there situations where events could be dispatched + // in a server (non-DOM) environment? + const event = custom_event(type, detail); + callbacks.slice().forEach(fn => { + fn.call(component, event); + }); + } + }; } - function setContext(key, context) { - get_current_component().$$.context.set(key, context); + get_current_component().$$.context.set(key, context); } - function getContext(key) { - return get_current_component().$$.context.get(key); + return get_current_component().$$.context.get(key); } +// source: https://html.spec.whatwg.org/multipage/indices.html +const boolean_attributes = new Set([ + 'allowfullscreen', + 'allowpaymentrequest', + 'async', + 'autofocus', + 'autoplay', + 'checked', + 'controls', + 'default', + 'defer', + 'disabled', + 'formnovalidate', + 'hidden', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nomodule', + 'novalidate', + 'open', + 'playsinline', + 'readonly', + 'required', + 'reversed', + 'selected' +]); + const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://infra.spec.whatwg.org/#noncharacter - -function spread(args) { - const attributes = Object.assign({}, ...args); - let str = ''; - - Object.keys(attributes).forEach(name => { - if (invalid_attribute_name_character.test(name)) return; - - const value = attributes[name]; - if (value === undefined) return; - if (value === true) str += " " + name; - - const escaped = String(value) - .replace(/"/g, '"') - .replace(/'/g, '''); - - str += " " + name + "=" + JSON.stringify(escaped); - }); - - return str; +function spread(args, classes_to_add) { + const attributes = Object.assign({}, ...args); + if (classes_to_add) { + if (attributes.class == null) { + attributes.class = classes_to_add; + } + else { + attributes.class += ' ' + classes_to_add; + } + } + let str = ''; + Object.keys(attributes).forEach(name => { + if (invalid_attribute_name_character.test(name)) + return; + const value = attributes[name]; + if (value === true) + str += " " + name; + else if (boolean_attributes.has(name.toLowerCase())) { + if (value) + str += " " + name; + } + else if (value != null) { + str += ` ${name}="${String(value).replace(/"/g, '"').replace(/'/g, ''')}"`; + } + }); + return str; } - const escaped = { - '"': '"', - "'": ''', - '&': '&', - '<': '<', - '>': '>' + '"': '"', + "'": ''', + '&': '&', + '<': '<', + '>': '>' }; - function escape(html) { - return String(html).replace(/["'&<>]/g, match => escaped[match]); + return String(html).replace(/["'&<>]/g, match => escaped[match]); } - const missing_component = { - $$render: () => '' + $$render: () => '' }; - -function validate_component(component, name) { - if (!component || !component.$$render) { - if (name === 'svelte:component') name += ' this={...}'; - throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`); - } - - return component; -} - -let on_destroy; - -function create_ssr_component(fn) { - function $$render(result, props, bindings, slots) { - const parent_component = current_component; - - const $$ = { - on_destroy, - context: new Map(parent_component ? parent_component.$$.context : []), - - // these will be immediately discarded - on_mount: [], - before_render: [], - after_render: [], - callbacks: blank_object() - }; - - set_current_component({ $$ }); - - const html = fn(result, props, bindings, slots); - - set_current_component(parent_component); - return html; - } - - return { - render: (props = {}, options = {}) => { - on_destroy = []; - - const result = { head: '', css: new Set() }; - const html = $$render(result, props, {}, options); - - run_all(on_destroy); - - return { - html, - css: { - code: Array.from(result.css).map(css => css.code).join('\n'), - map: null // TODO - }, - head: result.head - }; - }, - - $$render - }; +function validate_component$1(component, name) { + if (!component || !component.$$render) { + if (name === 'svelte:component') + name += ' this={...}'; + throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`); + } + return component; } - -function get_store_value(store) { - let value; - store.subscribe(_ => value = _)(); - return value; +let on_destroy$1; +function create_ssr_component$1(fn) { + function $$render(result, props, bindings, slots) { + const parent_component = current_component$1; + const $$ = { + on_destroy: on_destroy$1, + context: new Map(parent_component ? parent_component.$$.context : []), + // these will be immediately discarded + on_mount: [], + before_update: [], + after_update: [], + callbacks: blank_object$1() + }; + set_current_component$1({ $$ }); + const html = fn(result, props, bindings, slots); + set_current_component$1(parent_component); + return html; + } + return { + render: (props = {}, options = {}) => { + on_destroy$1 = []; + const result = { title: '', head: '', css: new Set() }; + const html = $$render(result, props, {}, options); + run_all$1(on_destroy$1); + return { + html, + css: { + code: Array.from(result.css).map(css => css.code).join('\n'), + map: null // TODO + }, + head: result.title + result.head + }; + }, + $$render + }; } +const subscriber_queue = []; +/** + * Creates a `Readable` store that allows reading by subscription. + * @param value initial value + * @param {StartStopNotifier}start start and stop notifications for subscriptions + */ function readable(value, start) { - return { - subscribe: writable(value, start).subscribe - }; + return { + subscribe: writable(value, start).subscribe + }; } - +/** + * Create a `Writable` store that allows both updating and reading by subscription. + * @param {*=}value initial value + * @param {StartStopNotifier=}start start and stop notifications for subscriptions + */ function writable(value, start = noop) { - let stop; - const subscribers = []; - - function set(new_value) { - if (safe_not_equal(value, new_value)) { - value = new_value; - if (!stop) return; // not ready - subscribers.forEach(s => s[1]()); - subscribers.forEach(s => s[0](value)); - } - } - - function update(fn) { - set(fn(value)); - } - - function subscribe(run, invalidate = noop) { - const subscriber = [run, invalidate]; - subscribers.push(subscriber); - if (subscribers.length === 1) stop = start(set) || noop; - run(value); - - return () => { - const index = subscribers.indexOf(subscriber); - if (index !== -1) subscribers.splice(index, 1); - if (subscribers.length === 0) stop(); - }; - } - - return { set, update, subscribe }; + let stop; + const subscribers = []; + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { // store is ready + const run_queue = !subscriber_queue.length; + for (let i = 0; i < subscribers.length; i += 1) { + const s = subscribers[i]; + s[1](); + subscriber_queue.push(s, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + function update(fn) { + set(fn(value)); + } + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.push(subscriber); + if (subscribers.length === 1) { + stop = start(set) || noop; + } + run(value); + return () => { + const index = subscribers.indexOf(subscriber); + if (index !== -1) { + subscribers.splice(index, 1); + } + if (subscribers.length === 0) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; } - function derived(stores, fn, initial_value) { - const single = !Array.isArray(stores); - if (single) stores = [stores]; - - const auto = fn.length < 2; - - return readable(initial_value, set => { - let inited = false; - const values = []; - - let pending = 0; - - const sync = () => { - if (pending) return; - const result = fn(single ? values[0] : values, set); - if (auto) set(result); - }; - - const unsubscribers = stores.map((store, i) => store.subscribe( - value => { - values[i] = value; - pending &= ~(1 << i); - if (inited) sync(); - }, - () => { - pending |= (1 << i); - }) - ); - - inited = true; - sync(); - - return function stop() { - run_all(unsubscribers); - }; - }); + const single = !Array.isArray(stores); + const stores_array = single + ? [stores] + : stores; + const auto = fn.length < 2; + return readable(initial_value, (set) => { + let inited = false; + const values = []; + let pending = 0; + let cleanup = noop; + const sync = () => { + if (pending) { + return; + } + cleanup(); + const result = fn(single ? values[0] : values, set); + if (auto) { + set(result); + } + else { + cleanup = is_function(result) ? result : noop; + } + }; + const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => { + values[i] = value; + pending &= ~(1 << i); + if (inited) { + sync(); + } + }, () => { + pending |= (1 << i); + })); + inited = true; + sync(); + return function stop() { + run_all$1(unsubscribers); + cleanup(); + }; + }); } const LOCATION = {}; @@ -367,6 +489,7 @@ function startsWith(string, search) { function isRootSegment(segment) { return segment === ""; } + /** * Check if `segment` is a dynamic segment * @param {string} segment @@ -375,13 +498,14 @@ function isRootSegment(segment) { function isDynamic(segment) { return paramRe.test(segment); } + /** * Check if `segment` is a splat * @param {string} segment * @return {boolean} */ function isSplat(segment) { - return segment === "*"; + return segment[0] === "*"; } /** @@ -417,20 +541,20 @@ function rankRoute(route, index) { const score = route.default ? 0 : segmentize(route.path).reduce((score, segment) => { - score += SEGMENT_POINTS; - - if (isRootSegment(segment)) { - score += ROOT_POINTS; - } else if (isDynamic(segment)) { - score += DYNAMIC_POINTS; - } else if (isSplat(segment)) { - score -= SEGMENT_POINTS + SPLAT_PENALTY; - } else { - score += STATIC_POINTS; - } + score += SEGMENT_POINTS; + + if (isRootSegment(segment)) { + score += ROOT_POINTS; + } else if (isDynamic(segment)) { + score += DYNAMIC_POINTS; + } else if (isSplat(segment)) { + score -= SEGMENT_POINTS + SPLAT_PENALTY; + } else { + score += STATIC_POINTS; + } - return score; - }, 0); + return score; + }, 0); return { route, score, index }; } @@ -504,12 +628,13 @@ function pick(routes, uri) { const routeSegment = routeSegments[index]; const uriSegment = uriSegments[index]; - let isSplat = routeSegment === "*"; - if (isSplat) { + if (routeSegment !== undefined && isSplat(routeSegment)) { // Hit a splat, just grab the rest, and return a match // uri: /files/documents/work - // route: /files/* - params["*"] = uriSegments + // route: /files/* or /files/*splatname + const splatName = routeSegment === "*" ? "*" : routeSegment.slice(1); + + params[splatName] = uriSegments .slice(index) .map(decodeURIComponent) .join("/"); @@ -649,346 +774,394 @@ function resolve(to, base) { function combinePaths(basepath, path) { return `${stripSlashes( path === "/" ? basepath : `${stripSlashes(basepath)}/${stripSlashes(path)}` - )}/*`; + )}/`; } -/* node_modules/svelte-routing/src/Router.svelte generated by Svelte v3.2.2 */ +async function checkRouteGuards(route) { + const shouldActivate = ((typeof route.canActivate == "boolean" && !route.canActivate) || + (typeof route.canActivate == "function" && !(await route.canActivate()))) ? false : true; -const Router = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { - let $base, $location, $routes; + const shouldDeactivate = ((typeof route.canDeactivate == "boolean" && !route.canDeactivate) || + (typeof route.canDeactivate == "function" && !(await route.canDeactivate()))) ? false : true; - + return { + shouldActivate, + shouldDeactivate + }; +} - let { basepath = "/", url = null } = $$props; +/* C:\Users\loren\personal-projects\svelte-routing\src\Router.svelte generated by Svelte v3.28.0 */ + +const Router = create_ssr_component$1(($$result, $$props, $$bindings, slots) => { + let $base; + let $location; + let $routes; + let { basepath = "/" } = $$props; + let { url = null } = $$props; + const locationContext = getContext(LOCATION); + const routerContext = getContext(ROUTER); + const routes = writable([]); + $routes = get_store_value(routes); + const activeRoute = writable(null); + let hasActiveRoute = false; // Used in SSR to synchronously set that a Route is active. - const locationContext = getContext(LOCATION); - const routerContext = getContext(ROUTER); + // If locationContext is not set, this is the topmost Router in the tree. + // If the `url` prop is given we force the location to it. + const location = locationContext || writable(url ? { pathname: url } : globalHistory.location); - const routes = writable([]); $routes = get_store_value(routes); - const activeRoute = writable(null); - let hasActiveRoute = false; // Used in SSR to synchronously set that a Route is active. + $location = get_store_value(location); - // If locationContext is not set, this is the topmost Router in the tree. - // If the `url` prop is given we force the location to it. - const location = - locationContext || - writable(url ? { pathname: url } : globalHistory.location); $location = get_store_value(location); + // If routerContext is set, the routerBase of the parent Router + // will be the base for this Router's descendants. + // If routerContext is not set, the path and resolved uri will both + // have the value of the basepath prop. + const base = routerContext + ? routerContext.routerBase + : writable({ path: basepath, uri: basepath }); - // If routerContext is set, the routerBase of the parent Router - // will be the base for this Router's descendants. - // If routerContext is not set, the path and resolved uri will both - // have the value of the basepath prop. - const base = routerContext - ? routerContext.routerBase - : writable({ - path: basepath, - uri: basepath - }); $base = get_store_value(base); + $base = get_store_value(base); - const routerBase = derived([base, activeRoute], ([base, activeRoute]) => { - // If there is no activeRoute, the routerBase will be identical to the base. - if (activeRoute === null) { - return base; - } + const routerBase = derived([base, activeRoute], ([base, activeRoute]) => { + // If there is no activeRoute, the routerBase will be identical to the base. + if (activeRoute === null) { + return base; + } - const { path: basepath } = base; - const { route, uri } = activeRoute; - // Remove the /* from the end for child Routes relative paths. - const path = route.default ? basepath : route.path.replace(/\*$/, ""); + const { path: basepath } = base; + const { route, uri } = activeRoute; - return { path, uri }; - }); + // Remove the potential /* or /*splatname from + // the end of the child Routes relative paths. + const path = route.default + ? basepath + : route.path.replace(/\*.*$/, ""); - function registerRoute(route) { - const { path: basepath } = $base; - let { path } = route; - - // We store the original path in the _path property so we can reuse - // it when the basepath changes. The only thing that matters is that - // the route reference is intact, so mutation is fine. - route._path = path; - route.path = combinePaths(basepath, path); - - if (typeof window === "undefined") { - // In SSR we should set the activeRoute immediately if it is a match. - // If there are more Routes being registered after a match is found, - // we just skip them. - if (hasActiveRoute) { - return; - } + return { path, uri }; + }); - const matchingRoute = match(route, $location.pathname); - if (matchingRoute) { - activeRoute.set(matchingRoute); - hasActiveRoute = true; - } - } else { - routes.update(rs => { - rs.push(route); - return rs; - }); - } - } + function registerRoute(route) { + const { path: basepath } = $base; + let { path } = route; + + // We store the original path in the _path property so we can reuse + // it when the basepath changes. The only thing that matters is that + // the route reference is intact, so mutation is fine. + route._path = path; + + route.path = combinePaths(basepath, path); + + if (typeof window === "undefined") { + // In SSR we should set the activeRoute immediately if it is a match. + // If there are more Routes being registered after a match is found, + // we just skip them. + if (hasActiveRoute) { + return; + } + + const matchingRoute = match(route, $location.pathname); + + if (matchingRoute) { + activeRoute.set(matchingRoute); + hasActiveRoute = true; + } + } else { + routes.update(rs => { + rs.push(route); + return rs; + }); + } + } - function unregisterRoute(route) { - routes.update(rs => { - const index = rs.indexOf(route); - rs.splice(index, 1); - return rs; - }); - } + function unregisterRoute(route) { + routes.update(rs => { + const index = rs.indexOf(route); + rs.splice(index, 1); + return rs; + }); + } - if (!locationContext) { - // The topmost Router in the tree is responsible for updating - // the location store and supplying it through context. - onMount(() => { - const unlisten = globalHistory.listen(history => { - location.set(history.location); - }); + if (!locationContext) { + // The topmost Router in the tree is responsible for updating + // the location store and supplying it through context. + onMount(() => { + const unlisten = globalHistory.listen(history => { + location.set(history.location); + }); - return unlisten; - }); + return unlisten; + }); - setContext(LOCATION, location); - } + setContext(LOCATION, location); + } - setContext(ROUTER, { - activeRoute, - base, - routerBase, - registerRoute, - unregisterRoute - }); + setContext(ROUTER, { + activeRoute, + routes, + base, + routerBase, + registerRoute, + unregisterRoute + }); if ($$props.basepath === void 0 && $$bindings.basepath && basepath !== void 0) $$bindings.basepath(basepath); if ($$props.url === void 0 && $$bindings.url && url !== void 0) $$bindings.url(url); - $base = get_store_value(base); $location = get_store_value(location); $routes = get_store_value(routes); - { - const { path: basepath } = $base; - routes.update(rs => { - rs.forEach(r => (r.path = combinePaths(basepath, r._path))); - return rs; - }); - } - { - const bestMatch = pick($routes, $location.pathname); - activeRoute.set(bestMatch); - } - - return `${$$slots.default ? $$slots.default() : ``}`; -}); - -/* node_modules/svelte-routing/src/Route.svelte generated by Svelte v3.2.2 */ + { + { + const { path: basepath } = $base; -const Route = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { - let $activeRoute; + routes.update(rs => { + rs.forEach(r => r.path = combinePaths(basepath, r._path)); + return rs; + }); + } + } - + { + { + const bestMatch = pick($routes, $location.pathname); + activeRoute.set(bestMatch); + } + } - let { path = "", component = null } = $$props; + return `${slots.default ? slots.default({}) : ``}`; +}); - const { registerRoute, unregisterRoute, activeRoute } = getContext(ROUTER); $activeRoute = get_store_value(activeRoute); +/* C:\Users\loren\personal-projects\svelte-routing\src\Route.svelte generated by Svelte v3.28.0 */ - const route = { - path, - // If no path prop is given, this Route will act as the default Route - // that is rendered if no other Route in the Router is a match. - default: path === "" - }; - let routeParams = {}; +const Route = create_ssr_component$1(($$result, $$props, $$bindings, slots) => { + let $activeRoute; + let $location; + let { path = "" } = $$props; + let { component = null } = $$props; + let { canActivate = null } = $$props; + let { canDeactivate = null } = $$props; + const { registerRoute, unregisterRoute, activeRoute } = getContext(ROUTER); + $activeRoute = get_store_value(activeRoute); + const location = getContext(LOCATION); + $location = get_store_value(location); - registerRoute(route); + const route = { + path, + canActivate, + canDeactivate, + // If no path prop is given, this Route will act as the default Route + // that is rendered if no other Route in the Router is a match. + default: path === "" + }; - // There is no need to unregister Routes in SSR since it will all be - // thrown away anyway. - if (typeof window !== "undefined") { - onDestroy(() => { - unregisterRoute(route); - }); - } + let routeParams = {}; + let routeProps = {}; + let shouldActivate = false; + let shouldDeactivate = false; + registerRoute(route); + + // There is no need to unregister Routes in SSR since it will all be + // thrown away anyway. + if (typeof window !== "undefined") { + onDestroy(() => { + unregisterRoute(route); + }); + } if ($$props.path === void 0 && $$bindings.path && path !== void 0) $$bindings.path(path); if ($$props.component === void 0 && $$bindings.component && component !== void 0) $$bindings.component(component); - + if ($$props.canActivate === void 0 && $$bindings.canActivate && canActivate !== void 0) $$bindings.canActivate(canActivate); + if ($$props.canDeactivate === void 0 && $$bindings.canDeactivate && canDeactivate !== void 0) $$bindings.canDeactivate(canDeactivate); $activeRoute = get_store_value(activeRoute); + $location = get_store_value(location); - if ($activeRoute && $activeRoute.route === route) { - const { params } = $activeRoute; - routeParams = Object.keys(params).reduce((acc, param) => { - if (param !== "*") { - acc[param] = params[param]; - } - return acc; - }, {}); - } - - return `${ $activeRoute !== null && $activeRoute.route === route ? `${ component !== null ? `${validate_component(((component) || missing_component), 'svelte:component').$$render($$result, Object.assign(routeParams), {}, {})}` : `${$$slots.default ? $$slots.default() : ``}` }` : `` }`; -}); - -/* node_modules/svelte-routing/src/Link.svelte generated by Svelte v3.2.2 */ - -const Link = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { - let $base, $location; + { + (async () => { + if ($activeRoute && $activeRoute.route === route) { + routeParams = $activeRoute.params; + const guards = await checkRouteGuards($activeRoute.route); + shouldActivate = guards.shouldActivate; + shouldDeactivate = guards.shouldDeactivate; + } + })(); + } - + { + { + const { path, component, ...rest } = $$props; + routeProps = rest; + } + } - let { to = "#", replace = false, state = {}, getProps = () => ({}) } = $$props; + return `${$activeRoute !== null && $activeRoute.route === route + ? `${component !== null && shouldActivate + ? `${validate_component$1(component || missing_component, "svelte:component").$$render($$result, Object.assign({ location: $location }, routeParams, routeProps), {}, {})}` + : `${slots.default + ? slots.default({ + params: routeParams, + location: $location, + shouldActivate, + shouldDeactivate + }) + : ``}`}` + : ``}`; +}); - const { base } = getContext(ROUTER); $base = get_store_value(base); - const location = getContext(LOCATION); $location = get_store_value(location); +/* C:\Users\loren\personal-projects\svelte-routing\src\Link.svelte generated by Svelte v3.28.0 */ - let href, isPartiallyCurrent, isCurrent, props; +const Link = create_ssr_component$1(($$result, $$props, $$bindings, slots) => { + let $base; + let $location; + let $activeRoute; + let $routes; + let { to = "#" } = $$props; + let { replace = false } = $$props; + let { state = {} } = $$props; + let { getProps = () => ({}) } = $$props; + const { base, routes, activeRoute } = getContext(ROUTER); + $base = get_store_value(base); + $routes = get_store_value(routes); + $activeRoute = get_store_value(activeRoute); + const location = getContext(LOCATION); + $location = get_store_value(location); + const dispatch = createEventDispatcher(); + let href, isPartiallyCurrent, isCurrent, props; if ($$props.to === void 0 && $$bindings.to && to !== void 0) $$bindings.to(to); if ($$props.replace === void 0 && $$bindings.replace && replace !== void 0) $$bindings.replace(replace); if ($$props.state === void 0 && $$bindings.state && state !== void 0) $$bindings.state(state); if ($$props.getProps === void 0 && $$bindings.getProps && getProps !== void 0) $$bindings.getProps(getProps); - $base = get_store_value(base); $location = get_store_value(location); - + $activeRoute = get_store_value(activeRoute); + $routes = get_store_value(routes); + let ariaCurrent; href = to === "/" ? $base.uri : resolve(to, $base.uri); isPartiallyCurrent = startsWith($location.pathname, href); isCurrent = href === $location.pathname; - let ariaCurrent = isCurrent ? "page" : undefined; + ariaCurrent = isCurrent ? "page" : undefined; + props = getProps({ - location: $location, - href, - isPartiallyCurrent, - isCurrent - }); - - return ` - ${$$slots.default ? $$slots.default() : ``} - `; + location: $location, + href, + isPartiallyCurrent, + isCurrent + }); + + return `${slots.default ? slots.default({}) : ``}`; }); -/* src/components/NavLink.svelte generated by Svelte v3.2.2 */ +/* src\components\NavLink.svelte generated by Svelte v3.28.0 */ function getProps({ location, href, isPartiallyCurrent, isCurrent }) { - const isActive = href === "/" ? isCurrent : isPartiallyCurrent || isCurrent; + const isActive = href === "/" + ? isCurrent + : isPartiallyCurrent || isCurrent; - // The object returned here is spread on the anchor element's attributes - if (isActive) { - return { class: "active" }; - } - return {}; + // The object returned here is spread on the anchor element's attributes + if (isActive) { + return { class: "active" }; + } + + return {}; } -const NavLink = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const NavLink = create_ssr_component(($$result, $$props, $$bindings, slots) => { let { to = "" } = $$props; - if ($$props.to === void 0 && $$bindings.to && to !== void 0) $$bindings.to(to); - return `${validate_component(Link, 'Link').$$render($$result, { to: to, getProps: getProps }, {}, { - default: () => ` - ${$$slots.default ? $$slots.default() : ``} - ` + return `${validate_component(Link, "Link").$$render($$result, { to, getProps }, {}, { + default: () => `${slots.default ? slots.default({}) : ``}` })}`; }); -/* src/routes/Home.svelte generated by Svelte v3.2.2 */ +/* src\routes\Home.svelte generated by Svelte v3.28.0 */ -const Home = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const Home = create_ssr_component(($$result, $$props, $$bindings, slots) => { return `

Home

-

Welcome to my website

`; +

Welcome to my website

`; }); -/* src/routes/About.svelte generated by Svelte v3.2.2 */ +/* src\routes\About.svelte generated by Svelte v3.28.0 */ -const About = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const About = create_ssr_component(($$result, $$props, $$bindings, slots) => { return `

About

-

I like to code

`; +

I like to code

`; }); -/* src/routes/Blog.svelte generated by Svelte v3.2.2 */ - -const Blog = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { - return `${validate_component(Router, 'Router').$$render($$result, {}, {}, { - default: () => ` -

Blog

- -
    -
  • ${validate_component(Link, 'Link').$$render($$result, { to: "first" }, {}, { - default: () => `Today I did something cool` - })}
  • -
  • ${validate_component(Link, 'Link').$$render($$result, { to: "second" }, {}, { - default: () => `I did something awesome today` - })}
  • -
  • ${validate_component(Link, 'Link').$$render($$result, { to: "third" }, {}, { - default: () => `Did something sweet today` - })}
  • -
- - ${validate_component(Route, 'Route').$$render($$result, { path: "first" }, {}, { - default: () => ` -

- I did something cool today. Lorem ipsum dolor sit amet, consectetur - adipisicing elit. Quisquam rerum asperiores, ex animi sunt ipsum. Voluptas - sint id hic. Vel neque maxime exercitationem facere culpa nisi, nihil - incidunt quo nostrum, beatae dignissimos dolores natus quaerat! Quasi sint - praesentium inventore quidem, deserunt atque ipsum similique dolores maiores - expedita, qui totam. Totam et incidunt assumenda quas explicabo corporis - eligendi amet sint ducimus, culpa fugit esse. Tempore dolorum sit - perspiciatis corporis molestias nemo, veritatis, asperiores earum! - Ex repudiandae aperiam asperiores esse minus veniam sapiente corrupti - alias deleniti excepturi saepe explicabo eveniet harum fuga numquam - nostrum adipisci pariatur iusto sint, impedit provident repellat quis? -

- ` - })} - ${validate_component(Route, 'Route').$$render($$result, { path: "second" }, {}, { - default: () => ` -

- I did something awesome today. Lorem ipsum dolor sit amet, consectetur - adipisicing elit. Repudiandae enim quasi animi, vero deleniti dignissimos - sapiente perspiciatis. Veniam, repellendus, maiores. -

- ` - })} - ${validate_component(Route, 'Route').$$render($$result, { path: "third" }, {}, { - default: () => ` -

- I did something sweet today. Lorem ipsum dolor sit amet, consectetur - adipisicing elit. Modi ad voluptas rem consequatur commodi minima doloribus - veritatis nam, quas, culpa autem repellat saepe quam deleniti maxime delectus - fuga totam libero sit neque illo! Sapiente consequatur rem minima expedita - nemo blanditiis, aut veritatis alias nostrum vel? Esse molestias placeat, - doloribus commodi. -

- ` - })} - ` +/* src\routes\Blog.svelte generated by Svelte v3.28.0 */ + +const Blog = create_ssr_component(($$result, $$props, $$bindings, slots) => { + return `${validate_component(Router, "Router").$$render($$result, {}, {}, { + default: () => `

Blog

+ +
  • ${validate_component(Link, "Link").$$render($$result, { to: "first" }, {}, { + default: () => `Today I did something cool` + })}
  • +
  • ${validate_component(Link, "Link").$$render($$result, { to: "second" }, {}, { + default: () => `I did something awesome today` + })}
  • +
  • ${validate_component(Link, "Link").$$render($$result, { to: "third" }, {}, { + default: () => `Did something sweet today` + })}
+ + ${validate_component(Route, "Route").$$render($$result, { path: "first" }, {}, { + default: () => `

I did something cool today. Lorem ipsum dolor sit amet, consectetur + adipisicing elit. Quisquam rerum asperiores, ex animi sunt ipsum. Voluptas + sint id hic. Vel neque maxime exercitationem facere culpa nisi, nihil + incidunt quo nostrum, beatae dignissimos dolores natus quaerat! Quasi sint + praesentium inventore quidem, deserunt atque ipsum similique dolores + maiores expedita, qui totam. Totam et incidunt assumenda quas explicabo + corporis eligendi amet sint ducimus, culpa fugit esse. Tempore dolorum sit + perspiciatis corporis molestias nemo, veritatis, asperiores earum! Ex + repudiandae aperiam asperiores esse minus veniam sapiente corrupti alias + deleniti excepturi saepe explicabo eveniet harum fuga numquam nostrum + adipisci pariatur iusto sint, impedit provident repellat quis? +

` + })} + ${validate_component(Route, "Route").$$render($$result, { path: "second" }, {}, { + default: () => `

I did something awesome today. Lorem ipsum dolor sit amet, consectetur + adipisicing elit. Repudiandae enim quasi animi, vero deleniti dignissimos + sapiente perspiciatis. Veniam, repellendus, maiores. +

` + })} + ${validate_component(Route, "Route").$$render($$result, { path: "third" }, {}, { + default: () => `

I did something sweet today. Lorem ipsum dolor sit amet, consectetur + adipisicing elit. Modi ad voluptas rem consequatur commodi minima + doloribus veritatis nam, quas, culpa autem repellat saepe quam deleniti + maxime delectus fuga totam libero sit neque illo! Sapiente consequatur rem + minima expedita nemo blanditiis, aut veritatis alias nostrum vel? Esse + molestias placeat, doloribus commodi. +

` + })}` })}`; }); -/* src/App.svelte generated by Svelte v3.2.2 */ - -const App = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { - +/* src\App.svelte generated by Svelte v3.28.0 */ - // Used for SSR. A falsy value is ignored by the Router. - let { url = "" } = $$props; +async function check() { + return false; +} +const App = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { url = "" } = $$props; if ($$props.url === void 0 && $$bindings.url && url !== void 0) $$bindings.url(url); - return `${validate_component(Router, 'Router').$$render($$result, { url: url }, {}, { - default: () => ` - -
- ${validate_component(Route, 'Route').$$render($$result, { path: "about", component: About }, {}, {})} - ${validate_component(Route, 'Route').$$render($$result, { path: "blog", component: Blog }, {}, {})} - ${validate_component(Route, 'Route').$$render($$result, { path: "/", component: Home }, {}, {})} -
- ` + return `${validate_component(Router, "Router").$$render($$result, { url }, {}, { + default: () => ` +
${validate_component(Route, "Route").$$render( + $$result, + { + path: "about", + component: About, + canActivate: check + }, + {}, + {} + )} + ${validate_component(Route, "Route").$$render($$result, { path: "blog/*", component: Blog }, {}, {})} + ${validate_component(Route, "Route").$$render($$result, { path: "/", component: Home }, {}, {})}
` })}`; }); diff --git a/src/Link.svelte b/src/Link.svelte index ff9eddf..97ead19 100644 --- a/src/Link.svelte +++ b/src/Link.svelte @@ -2,14 +2,20 @@ import { getContext, createEventDispatcher } from "svelte"; import { ROUTER, LOCATION } from "./contexts.js"; import { navigate } from "./history.js"; - import { startsWith, resolve, shouldNavigate } from "./utils.js"; + import { + startsWith, + resolve, + shouldNavigate, + checkRouteGuards, + pick, + } from "./utils.js"; export let to = "#"; export let replace = false; export let state = {}; export let getProps = () => ({}); - const { base } = getContext(ROUTER); + const { base, routes, activeRoute } = getContext(ROUTER); const location = getContext(LOCATION); const dispatch = createEventDispatcher(); @@ -22,12 +28,27 @@ location: $location, href, isPartiallyCurrent, - isCurrent + isCurrent, }); - function onClick(event) { + async function onClick(event) { dispatch("click", event); + if ($activeRoute) { + const { shouldDeactivate } = await checkRouteGuards($activeRoute.route); + if (!shouldDeactivate) { + event.preventDefault(); + return; + } + } + + const { route: nextRoute } = pick($routes, href); + const { shouldActivate } = await checkRouteGuards(nextRoute); + if (!shouldActivate) { + event.preventDefault(); + return; + } + if (shouldNavigate(event)) { event.preventDefault(); // Don't push another entry to the history stack when the user @@ -38,6 +59,6 @@ } - - + + diff --git a/src/Route.svelte b/src/Route.svelte index 3f411b2..61aa4e4 100644 --- a/src/Route.svelte +++ b/src/Route.svelte @@ -1,25 +1,38 @@ {#if $activeRoute !== null && $activeRoute.route === route} - {#if component !== null} - + {#if component !== null && shouldActivate} + {:else} - + {/if} {/if} diff --git a/src/Router.svelte b/src/Router.svelte index ce98655..da498e7 100644 --- a/src/Router.svelte +++ b/src/Router.svelte @@ -29,7 +29,7 @@ ? routerContext.routerBase : writable({ path: basepath, - uri: basepath + uri: basepath, }); const routerBase = derived([base, activeRoute], ([base, activeRoute]) => { @@ -71,7 +71,7 @@ hasActiveRoute = true; } } else { - routes.update(rs => { + routes.update((rs) => { rs.push(route); return rs; }); @@ -79,7 +79,7 @@ } function unregisterRoute(route) { - routes.update(rs => { + routes.update((rs) => { const index = rs.indexOf(route); rs.splice(index, 1); return rs; @@ -90,8 +90,8 @@ // the basepath changes. $: { const { path: basepath } = $base; - routes.update(rs => { - rs.forEach(r => (r.path = combinePaths(basepath, r._path))); + routes.update((rs) => { + rs.forEach((r) => (r.path = combinePaths(basepath, r._path))); return rs; }); } @@ -108,7 +108,7 @@ // The topmost Router in the tree is responsible for updating // the location store and supplying it through context. onMount(() => { - const unlisten = globalHistory.listen(history => { + const unlisten = globalHistory.listen((history) => { location.set(history.location); }); @@ -120,11 +120,12 @@ setContext(ROUTER, { activeRoute, + routes, base, routerBase, registerRoute, - unregisterRoute + unregisterRoute, }); - + diff --git a/src/utils.js b/src/utils.js index 419f4f2..e672d0b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -82,20 +82,20 @@ function rankRoute(route, index) { const score = route.default ? 0 : segmentize(route.path).reduce((score, segment) => { - score += SEGMENT_POINTS; - - if (isRootSegment(segment)) { - score += ROOT_POINTS; - } else if (isDynamic(segment)) { - score += DYNAMIC_POINTS; - } else if (isSplat(segment)) { - score -= SEGMENT_POINTS + SPLAT_PENALTY; - } else { - score += STATIC_POINTS; - } + score += SEGMENT_POINTS; + + if (isRootSegment(segment)) { + score += ROOT_POINTS; + } else if (isDynamic(segment)) { + score += DYNAMIC_POINTS; + } else if (isSplat(segment)) { + score -= SEGMENT_POINTS + SPLAT_PENALTY; + } else { + score += STATIC_POINTS; + } - return score; - }, 0); + return score; + }, 0); return { route, score, index }; } @@ -340,4 +340,17 @@ function hostMatches(anchor) { ) } -export { stripSlashes, pick, match, resolve, combinePaths, shouldNavigate, hostMatches }; +async function checkRouteGuards(route) { + const shouldActivate = ((typeof route.canActivate == "boolean" && !route.canActivate) || + (typeof route.canActivate == "function" && !(await route.canActivate()))) ? false : true + + const shouldDeactivate = ((typeof route.canDeactivate == "boolean" && !route.canDeactivate) || + (typeof route.canDeactivate == "function" && !(await route.canDeactivate()))) ? false : true + + return { + shouldActivate, + shouldDeactivate + }; +} + +export { stripSlashes, pick, match, resolve, combinePaths, shouldNavigate, hostMatches, checkRouteGuards };