vue-router

/*!

* vue-router v4.0.13

* (c) 2022 Eduardo San Martin Morote

* @license MIT

*/

var VueRouter = (function (exports, vue) {

'use strict';

const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';

const PolySymbol = (name) =>

// vr = vue router

hasSymbol

? Symbol('[vue-router]: ' + name )

: ('[vue-router]: ' ) + name;

// rvlm = Router View Location Matched

/**

* RouteRecord being rendered by the closest ancestor Router View. Used for

* `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View

* Location Matched

*

* @internal

*/

const matchedRouteKey = /*#__PURE__*/ PolySymbol('router view location matched' );

/**

* Allows overriding the router view depth to control which component in

* `matched` is rendered. rvd stands for Router View Depth

*

* @internal

*/

const viewDepthKey = /*#__PURE__*/ PolySymbol('router view depth' );

/**

* Allows overriding the router instance returned by `useRouter` in tests. r

* stands for router

*

* @internal

*/

const routerKey = /*#__PURE__*/ PolySymbol('router' );

/**

* Allows overriding the current route returned by `useRoute` in tests. rl

* stands for route location

*

* @internal

*/

const routeLocationKey = /*#__PURE__*/ PolySymbol('route location' );

/**

* Allows overriding the current route used by router-view. Internally this is

* used when the `route` prop is passed.

*

* @internal

*/

const routerViewLocationKey = /*#__PURE__*/ PolySymbol('router view location' );

const isBrowser = typeof window !== 'undefined';

function isESModule(obj) {

return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module');

}

const assign = Object.assign;

function applyToParams(fn, params) {

const newParams = {};

for (const key in params) {

const value = params[key];

newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value);

}

return newParams;

}

const noop = () => { };

function warn(msg) {

// avoid using ...args as it breaks in older Edge builds

const args = Array.from(arguments).slice(1);

console.warn.apply(console, ['[Vue Router warn]: ' + msg].concat(args));

}

const TRAILING_SLASH_RE = /\/$/;

const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');

/**

* Transforms an URI into a normalized history location

*

* @param parseQuery

* @param location - URI to normalize

* @param currentLocation - current absolute location. Allows resolving relative

* paths. Must start with `/`. Defaults to `/`

* @returns a normalized history location

*/

function parseURL(parseQuery, location, currentLocation = '/') {

let path, query = {}, searchString = '', hash = '';

// Could use URL and URLSearchParams but IE 11 doesn't support it

const searchPos = location.indexOf('?');

const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0);

if (searchPos > -1) {

path = location.slice(0, searchPos);

searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);

query = parseQuery(searchString);

}

if (hashPos > -1) {

path = path || location.slice(0, hashPos);

// keep the # character

hash = location.slice(hashPos, location.length);

}

// no search and no query

path = resolveRelativePath(path != null ? path : location, currentLocation);

// empty path means a relative query or hash `?foo=f`, `#thing`

return {

fullPath: path + (searchString && '?') + searchString + hash,

path,

query,

hash,

};

}

/**

* Stringifies a URL object

*

* @param stringifyQuery

* @param location

*/

function stringifyURL(stringifyQuery, location) {

const query = location.query ? stringifyQuery(location.query) : '';

return location.path + (query && '?') + query + (location.hash || '');

}

/**

* Strips off the base from the beginning of a location.pathname in a non

* case-sensitive way.

*

* @param pathname - location.pathname

* @param base - base to strip off

*/

function stripBase(pathname, base) {

// no base or base is not found at the beginning

if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase()))

return pathname;

return pathname.slice(base.length) || '/';

}

/**

* Checks if two RouteLocation are equal. This means that both locations are

* pointing towards the same {@link RouteRecord} and that all `params`, `query`

* parameters and `hash` are the same

*

* @param a - first {@link RouteLocation}

* @param b - second {@link RouteLocation}

*/

function isSameRouteLocation(stringifyQuery, a, b) {

const aLastIndex = a.matched.length - 1;

const bLastIndex = b.matched.length - 1;

return (aLastIndex > -1 &&

aLastIndex === bLastIndex &&

isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) &&

isSameRouteLocationParams(a.params, b.params) &&

stringifyQuery(a.query) === stringifyQuery(b.query) &&

a.hash === b.hash);

}

/**

* Check if two `RouteRecords` are equal. Takes into account aliases: they are

* considered equal to the `RouteRecord` they are aliasing.

*

* @param a - first {@link RouteRecord}

* @param b - second {@link RouteRecord}

*/

function isSameRouteRecord(a, b) {

// since the original record has an undefined value for aliasOf

// but all aliases point to the original record, this will always compare

// the original record

return (a.aliasOf || a) === (b.aliasOf || b);

}

function isSameRouteLocationParams(a, b) {

if (Object.keys(a).length !== Object.keys(b).length)

return false;

for (const key in a) {

if (!isSameRouteLocationParamsValue(a[key], b[key]))

return false;

}

return true;

}

function isSameRouteLocationParamsValue(a, b) {

return Array.isArray(a)

? isEquivalentArray(a, b)

: Array.isArray(b)

? isEquivalentArray(b, a)

: a === b;

}

/**

* Check if two arrays are the same or if an array with one single entry is the

* same as another primitive value. Used to check query and parameters

*

* @param a - array of values

* @param b - array of values or a single value

*/

function isEquivalentArray(a, b) {

return Array.isArray(b)

? a.length === b.length && a.every((value, i) => value === b[i])

: a.length === 1 && a[0] === b;

}

/**

* Resolves a relative path that starts with `.`.

*

* @param to - path location we are resolving

* @param from - currentLocation.path, should start with `/`

*/

function resolveRelativePath(to, from) {

if (to.startsWith('/'))

return to;

if (!from.startsWith('/')) {

warn(`Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`);

return to;

}

if (!to)

return from;

const fromSegments = from.split('/');

const toSegments = to.split('/');

let position = fromSegments.length - 1;

let toPosition;

let segment;

for (toPosition = 0; toPosition < toSegments.length; toPosition++) {

segment = toSegments[toPosition];

// can't go below zero

if (position === 1 || segment === '.')

continue;

if (segment === '..')

position--;

// found something that is not relative path

else

break;

}

return (fromSegments.slice(0, position).join('/') +

'/' +

toSegments

.slice(toPosition - (toPosition === toSegments.length ? 1 : 0))

.join('/'));

}

var NavigationType;

(function (NavigationType) {

NavigationType["pop"] = "pop";

NavigationType["push"] = "push";

})(NavigationType || (NavigationType = {}));

var NavigationDirection;

(function (NavigationDirection) {

NavigationDirection["back"] = "back";

NavigationDirection["forward"] = "forward";

NavigationDirection["unknown"] = "";

})(NavigationDirection || (NavigationDirection = {}));

/**

* Starting location for Histories

*/

const START = '';

// Generic utils

/**

* Normalizes a base by removing any trailing slash and reading the base tag if

* present.

*

* @param base - base to normalize

*/

function normalizeBase(base) {

if (!base) {

if (isBrowser) {

// respect tag

const baseEl = document.querySelector('base');

base = (baseEl && baseEl.getAttribute('href')) || '/';

// strip full URL origin

base = base.replace(/^\w+:\/\/[^\/]+/, '');

}

else {

base = '/';

}

}

// ensure leading slash when it was removed by the regex above avoid leading

// slash with hash because the file could be read from the disk like file://

// and the leading slash would cause problems

if (base[0] !== '/' && base[0] !== '#')

base = '/' + base;

// remove the trailing slash so all other method can just do `base + fullPath`

// to build an href

return removeTrailingSlash(base);

}

// remove any character before the hash

const BEFORE_HASH_RE = /^[^#]+#/;

function createHref(base, location) {

return base.replace(BEFORE_HASH_RE, '#') + location;

}

function getElementPosition(el, offset) {

const docRect = document.documentElement.getBoundingClientRect();

const elRect = el.getBoundingClientRect();

return {

behavior: offset.behavior,

left: elRect.left - docRect.left - (offset.left || 0),

top: elRect.top - docRect.top - (offset.top || 0),

};

}

const computeScrollPosition = () => ({

left: window.pageXOffset,

top: window.pageYOffset,

});

function scrollToPosition(position) {

let scrollToOptions;

if ('el' in position) {

const positionEl = position.el;

const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#');

/**

* `id`s can accept pretty much any characters, including CSS combinators

* like `>` or `~`. It's still possible to retrieve elements using

* `document.getElementById('~')` but it needs to be escaped when using

* `document.querySelector('#\\~')` for it to be valid. The only

* requirements for `id`s are them to be unique on the page and to not be

* empty (`id=""`). Because of that, when passing an id selector, it should

* be properly escaped for it to work with `querySelector`. We could check

* for the id selector to be simple (no CSS combinators `+ >~`) but that

* would make things inconsistent since they are valid characters for an

* `id` but would need to be escaped when using `querySelector`, breaking

* their usage and ending up in no selector returned. Selectors need to be

* escaped:

*

* - `#1-thing` becomes `#\31 -thing`

* - `#with~symbols` becomes `#with\\~symbols`

*

* - More information about the topic can be found at

* https://mathiasbynens.be/notes/html5-id-class.

* - Practical example: https://mathiasbynens.be/demo/html5-id

*/

if (typeof position.el === 'string') {

if (!isIdSelector || !document.getElementById(position.el.slice(1))) {

try {

const foundEl = document.querySelector(position.el);

if (isIdSelector && foundEl) {

warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`);

// return to avoid other warnings

return;

}

}

catch (err) {

warn(`The selector "${position.el}" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).`);

// return to avoid other warnings

return;

}

}

}

const el = typeof positionEl === 'string'

? isIdSelector

? document.getElementById(positionEl.slice(1))

: document.querySelector(positionEl)

: positionEl;

if (!el) {

warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`);

return;

}

scrollToOptions = getElementPosition(el, position);

}

else {

scrollToOptions = position;

}

if ('scrollBehavior' in document.documentElement.style)

window.scrollTo(scrollToOptions);

else {

window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.pageXOffset, scrollToOptions.top != null ? scrollToOptions.top : window.pageYOffset);

}

}

function getScrollKey(path, delta) {

const position = history.state ? history.state.position - delta : -1;

return position + path;

}

const scrollPositions = new Map();

function saveScrollPosition(key, scrollPosition) {

scrollPositions.set(key, scrollPosition);

}

function getSavedScrollPosition(key) {

const scroll = scrollPositions.get(key);

// consume it so it's not used again

scrollPositions.delete(key);

return scroll;

}

// TODO: RFC about how to save scroll position

/**

* ScrollBehavior instance used by the router to compute and restore the scroll

* position when navigating.

*/

// export interface ScrollHandler {

// // returns a scroll position that can be saved in history

// compute(): ScrollPositionEntry

// // can take an extended ScrollPositionEntry

// scroll(position: ScrollPosition): void

// }

// export const scrollHandler: ScrollHandler = {

// compute: computeScroll,

// scroll: scrollToPosition,

// }

let createBaseLocation = () => location.protocol + '//' + location.host;

/**

* Creates a normalized history location from a window.location object

* @param location -

*/

function createCurrentLocation(base, location) {

const { pathname, search, hash } = location;

// allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end

const hashPos = base.indexOf('#');

if (hashPos > -1) {

let slicePos = hash.includes(base.slice(hashPos))

? base.slice(hashPos).length

: 1;

let pathFromHash = hash.slice(slicePos);

// prepend the starting slash to hash so the url starts with /#

if (pathFromHash[0] !== '/')

pathFromHash = '/' + pathFromHash;

return stripBase(pathFromHash, '');

}

const path = stripBase(pathname, base);

return path + search + hash;

}

function useHistoryListeners(base, historyState, currentLocation, replace) {

let listeners = [];

let teardowns = [];

// TODO: should it be a stack? a Dict. Check if the popstate listener

// can trigger twice

let pauseState = null;

const popStateHandler = ({ state, }) => {

const to = createCurrentLocation(base, location);

const from = currentLocation.value;

const fromState = historyState.value;

let delta = 0;

if (state) {

currentLocation.value = to;

historyState.value = state;

// ignore the popstate and reset the pauseState

if (pauseState && pauseState === from) {

pauseState = null;

return;

}

delta = fromState ? state.position - fromState.position : 0;

}

else {

replace(to);

}

// console.log({ deltaFromCurrent })

// Here we could also revert the navigation by calling history.go(-delta)

// this listener will have to be adapted to not trigger again and to wait for the url

// to be updated before triggering the listeners. Some kind of validation function would also

// need to be passed to the listeners so the navigation can be accepted

// call all listeners

listeners.forEach(listener => {

listener(currentLocation.value, from, {

delta,

type: NavigationType.pop,

direction: delta

? delta > 0

? NavigationDirection.forward

: NavigationDirection.back

: NavigationDirection.unknown,

});

});

};

function pauseListeners() {

pauseState = currentLocation.value;

}

function listen(callback) {

// setup the listener and prepare teardown callbacks

listeners.push(callback);

const teardown = () => {

const index = listeners.indexOf(callback);

if (index > -1)

listeners.splice(index, 1);

};

teardowns.push(teardown);

return teardown;

}

function beforeUnloadListener() {

const { history } = window;

if (!history.state)

return;

history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');

}

function destroy() {

for (const teardown of teardowns)

teardown();

teardowns = [];

window.removeEventListener('popstate', popStateHandler);

window.removeEventListener('beforeunload', beforeUnloadListener);

}

// setup the listeners and prepare teardown callbacks

window.addEventListener('popstate', popStateHandler);

window.addEventListener('beforeunload', beforeUnloadListener);

return {

pauseListeners,

listen,

destroy,

};

}

/**

* Creates a state object

*/

function buildState(back, current, forward, replaced = false, computeScroll = false) {

return {

back,

current,

forward,

replaced,

position: window.history.length,

scroll: computeScroll ? computeScrollPosition() : null,

};

}

function useHistoryStateNavigation(base) {

const { history, location } = window;

// private variables

const currentLocation = {

value: createCurrentLocation(base, location),

};

const historyState = { value: history.state };

// build current history entry as this is a fresh navigation

if (!historyState.value) {

changeLocation(currentLocation.value, {

back: null,

current: currentLocation.value,

forward: null,

// the length is off by one, we need to decrease it

position: history.length - 1,

replaced: true,

// don't add a scroll as the user may have an anchor and we want

// scrollBehavior to be triggered without a saved position

scroll: null,

}, true);

}

function changeLocation(to, state, replace) {

/**

* if a base tag is provided and we are on a normal domain, we have to

* respect the provided `base` attribute because pushState() will use it and

* potentially erase anything before the `#` like at

* https://github.com/vuejs/router/issues/685 where a base of

* `/folder/#` but a base of `/` would erase the `/folder/` section. If

* there is no host, the `` tag makes no sense and if there isn't a

* base tag we can just use everything after the `#`.

*/

const hashIndex = base.indexOf('#');

const url = hashIndex > -1

? (location.host && document.querySelector('base')

? base

: base.slice(hashIndex)) + to

: createBaseLocation() + base + to;

try {

// BROWSER QUIRK

// NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds

history[replace ? 'replaceState' : 'pushState'](state, '', url);

historyState.value = state;

}

catch (err) {

{

warn('Error with push/replace State', err);

}

// Force the navigation, this also resets the call count

location[replace ? 'replace' : 'assign'](url);

}

}

function replace(to, data) {

const state = assign({}, history.state, buildState(historyState.value.back,

// keep back and forward entries but override current position

to, historyState.value.forward, true), data, { position: historyState.value.position });

changeLocation(to, state, true);

currentLocation.value = to;

}

function push(to, data) {

// Add to current entry the information of where we are going

// as well as saving the current position

const currentState = assign({},

// use current history state to gracefully handle a wrong call to

// history.replaceState

// https://github.com/vuejs/router/issues/366

historyState.value, history.state, {

forward: to,

scroll: computeScrollPosition(),

});

if (!history.state) {

warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +

`history.replaceState(history.state, '', url)\n\n` +

`You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`);

}

changeLocation(currentState.current, currentState, true);

const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);

changeLocation(to, state, false);

currentLocation.value = to;

}

return {

location: currentLocation,

state: historyState,

push,

replace,

};

}

/**

* Creates an HTML5 history. Most common history for single page applications.

*

* @param base -

*/

function createWebHistory(base) {

base = normalizeBase(base);

const historyNavigation = useHistoryStateNavigation(base);

const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);

function go(delta, triggerListeners = true) {

if (!triggerListeners)

historyListeners.pauseListeners();

history.go(delta);

}

const routerHistory = assign({

// it's overridden right after

location: '',

base,

go,

createHref: createHref.bind(null, base),

}, historyNavigation, historyListeners);

Object.defineProperty(routerHistory, 'location', {

enumerable: true,

get: () => historyNavigation.location.value,

});

Object.defineProperty(routerHistory, 'state', {

enumerable: true,

get: () => historyNavigation.state.value,

});

return routerHistory;

}

/**

* Creates a in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere.

* It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.

*

* @param base - Base applied to all urls, defaults to '/'

* @returns a history object that can be passed to the router constructor

*/

function createMemoryHistory(base = '') {

let listeners = [];

let queue = [START];

let position = 0;

base = normalizeBase(base);

function setLocation(location) {

position++;

if (position === queue.length) {

// we are at the end, we can simply append a new entry

queue.push(location);

}

else {

// we are in the middle, we remove everything from here in the queue

queue.splice(position);

queue.push(location);

}

}

function triggerListeners(to, from, { direction, delta }) {

const info = {

direction,

delta,

type: NavigationType.pop,

};

for (const callback of listeners) {

callback(to, from, info);

}

}

const routerHistory = {

// rewritten by Object.defineProperty

location: START,

// TODO: should be kept in queue

state: {},

base,

createHref: createHref.bind(null, base),

replace(to) {

// remove current entry and decrement position

queue.splice(position--, 1);

setLocation(to);

},

push(to, data) {

setLocation(to);

},

listen(callback) {

listeners.push(callback);

return () => {

const index = listeners.indexOf(callback);

if (index > -1)

listeners.splice(index, 1);

};

},

destroy() {

listeners = [];

queue = [START];

position = 0;

},

go(delta, shouldTrigger = true) {

const from = this.location;

const direction =

// we are considering delta === 0 going forward, but in abstract mode

// using 0 for the delta doesn't make sense like it does in html5 where

// it reloads the page

delta < 0 ? NavigationDirection.back : NavigationDirection.forward;

position = Math.max(0, Math.min(position + delta, queue.length - 1));

if (shouldTrigger) {

triggerListeners(this.location, from, {

direction,

delta,

});

}

},

};

Object.defineProperty(routerHistory, 'location', {

enumerable: true,

get: () => queue[position],

});

return routerHistory;

}

/**

* Creates a hash history. Useful for web applications with no host (e.g.

* `file://`) or when configuring a server to handle any URL is not possible.

*

* @param base - optional base to provide. Defaults to `location.pathname +

* location.search` If there is a `` tag in the `head`, its value will be

* ignored in favor of this parameter **but note it affects all the

* history.pushState() calls**, meaning that if you use a `` tag, it's

* `href` value **has to match this parameter** (ignoring anything after the

* `#`).

*

* @example

* ```js

* // at https://example.com/folder

* createWebHashHistory() // gives a url of `https://example.com/folder#`

* createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#`

* // if the `#` is provided in the base, it won't be added by `createWebHashHistory`

* createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/`

* // you should avoid doing this because it changes the original url and breaks copying urls

* createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#`

*

* // at file:///usr/etc/folder/index.html

* // for locations with no `host`, the base is ignored

* createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#`

* ```

*/

function createWebHashHistory(base) {

// Make sure this implementation is fine in terms of encoding, specially for IE11

// for `file://`, directly use the pathname and ignore the base

// location.pathname contains an initial `/` even at the root: `https://example.com`

base = location.host ? base || location.pathname + location.search : '';

// allow the user to provide a `#` in the middle: `/base/#/app`

if (!base.includes('#'))

base += '#';

if (!base.endsWith('#/') && !base.endsWith('#')) {

warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, '#')}".`);

}

return createWebHistory(base);

}

function isRouteLocation(route) {

return typeof route === 'string' || (route && typeof route === 'object');

}

function isRouteName(name) {

return typeof name === 'string' || typeof name === 'symbol';

}

/**

* Initial route location where the router is. Can be used in navigation guards

* to differentiate the initial navigation.

*

* @example

* ```js

* import { START_LOCATION } from 'vue-router'

*

* router.beforeEach((to, from) => {

* if (from === START_LOCATION) {

* // initial navigation

* }

* })

* ```

*/

const START_LOCATION_NORMALIZED = {

path: '/',

name: undefined,

params: {},

query: {},

hash: '',

fullPath: '/',

matched: [],

meta: {},

redirectedFrom: undefined,

};

const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol('navigation failure' );

/**

* Enumeration with all possible types for navigation failures. Can be passed to

* {@link isNavigationFailure} to check for specific failures.

*/

exports.NavigationFailureType = void 0;

(function (NavigationFailureType) {

/**

* An aborted navigation is a navigation that failed because a navigation

* guard returned `false` or called `next(false)`

*/

NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";

/**

* A cancelled navigation is a navigation that failed because a more recent

* navigation finished started (not necessarily finished).

*/

NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";

/**

* A duplicated navigation is a navigation that failed because it was

* initiated while already being at the exact same location.

*/

NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";

})(exports.NavigationFailureType || (exports.NavigationFailureType = {}));

// DEV only debug messages

const ErrorTypeMessages = {

[1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) {

return `No match for\n ${JSON.stringify(location)}${currentLocation

? '\nwhile being at\n' + JSON.stringify(currentLocation)

: ''}`;

},

[2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {

return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;

},

[4 /* NAVIGATION_ABORTED */]({ from, to }) {

return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;

},

[8 /* NAVIGATION_CANCELLED */]({ from, to }) {

return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;

},

[16 /* NAVIGATION_DUPLICATED */]({ from, to }) {

return `Avoided redundant navigation to current location: "${from.fullPath}".`;

},

};

function createRouterError(type, params) {

// keep full error messages in cjs versions

{

return assign(new Error(ErrorTypeMessages[type](params)), {

type,

[NavigationFailureSymbol]: true,

}, params);

}

}

function isNavigationFailure(error, type) {

return (error instanceof Error &&

NavigationFailureSymbol in error &&

(type == null || !!(error.type & type)));

}

const propertiesToLog = ['params', 'query', 'hash'];

function stringifyRoute(to) {

if (typeof to === 'string')

return to;

if ('path' in to)

return to.path;

const location = {};

for (const key of propertiesToLog) {

if (key in to)

location[key] = to[key];

}

return JSON.stringify(location, null, 2);

}

// default pattern for a param: non greedy everything but /

const BASE_PARAM_PATTERN = '[^/]+?';

const BASE_PATH_PARSER_OPTIONS = {

sensitive: false,

strict: false,

start: true,

end: true,

};

// Special Regex characters that must be escaped in static tokens

const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;

/**

* Creates a path parser from an array of Segments (a segment is an array of Tokens)

*

* @param segments - array of segments returned by tokenizePath

* @param extraOptions - optional options for the regexp

* @returns a PathParser

*/

function tokensToParser(segments, extraOptions) {

const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);

// the amount of scores is the same as the length of segments except for the root segment "/"

const score = [];

// the regexp as a string

let pattern = options.start ? '^' : '';

// extracted keys

const keys = [];

for (const segment of segments) {

// the root segment needs special treatment

const segmentScores = segment.length ? [] : [90 /* Root */];

// allow trailing slash

if (options.strict && !segment.length)

pattern += '/';

for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {

const token = segment[tokenIndex];

// resets the score if we are inside a sub segment /:a-other-:b

let subSegmentScore = 40 /* Segment */ +

(options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0);

if (token.type === 0 /* Static */) {

// prepend the slash if we are starting a new segment

if (!tokenIndex)

pattern += '/';

pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');

subSegmentScore += 40 /* Static */;

}

else if (token.type === 1 /* Param */) {

const { value, repeatable, optional, regexp } = token;

keys.push({

name: value,

repeatable,

optional,

});

const re = regexp ? regexp : BASE_PARAM_PATTERN;

// the user provided a custom regexp /:id(\\d+)

if (re !== BASE_PARAM_PATTERN) {

subSegmentScore += 10 /* BonusCustomRegExp */;

// make sure the regexp is valid before using it

try {

new RegExp(`(${re})`);

}

catch (err) {

throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +

err.message);

}

}

// when we repeat we must take care of the repeating leading slash

let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;

// prepend the slash if we are starting a new segment

if (!tokenIndex)

subPattern =

// avoid an optional / if there are more segments e.g. /:p?-static

// or /:p?-:p2

optional && segment.length < 2

? `(?:/${subPattern})`

: '/' + subPattern;

if (optional)

subPattern += '?';

pattern += subPattern;

subSegmentScore += 20 /* Dynamic */;

if (optional)

subSegmentScore += -8 /* BonusOptional */;

if (repeatable)

subSegmentScore += -20 /* BonusRepeatable */;

if (re === '.*')

subSegmentScore += -50 /* BonusWildcard */;

}

segmentScores.push(subSegmentScore);

}

// an empty array like /home/ -> [[{home}], []]

// if (!segment.length) pattern += '/'

score.push(segmentScores);

}

// only apply the strict bonus to the last score

if (options.strict && options.end) {

const i = score.length - 1;

score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */;

}

// TODO: dev only warn double trailing slash

if (!options.strict)

pattern += '/?';

if (options.end)

pattern += '$';

// allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else

else if (options.strict)

pattern += '(?:/|$)';

const re = new RegExp(pattern, options.sensitive ? '' : 'i');

function parse(path) {

const match = path.match(re);

const params = {};

if (!match)

return null;

for (let i = 1; i < match.length; i++) {

const value = match[i] || '';

const key = keys[i - 1];

params[key.name] = value && key.repeatable ? value.split('/') : value;

}

return params;

}

function stringify(params) {

let path = '';

// for optional parameters to allow to be empty

let avoidDuplicatedSlash = false;

for (const segment of segments) {

if (!avoidDuplicatedSlash || !path.endsWith('/'))

path += '/';

avoidDuplicatedSlash = false;

for (const token of segment) {

if (token.type === 0 /* Static */) {

path += token.value;

}

else if (token.type === 1 /* Param */) {

const { value, repeatable, optional } = token;

const param = value in params ? params[value] : '';

if (Array.isArray(param) && !repeatable)

throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);

const text = Array.isArray(param) ? param.join('/') : param;

if (!text) {

if (optional) {

// if we have more than one optional param like /:a?-static we

// don't need to care about the optional param

if (segment.length < 2) {

// remove the last slash as we could be at the end

if (path.endsWith('/'))

path = path.slice(0, -1);

// do not append a slash on the next iteration

else

avoidDuplicatedSlash = true;

}

}

else

throw new Error(`Missing required param "${value}"`);

}

path += text;

}

}

}

return path;

}

return {

re,

score,

keys,

parse,

stringify,

};

}

/**

* Compares an array of numbers as used in PathParser.score and returns a

* number. This function can be used to `sort` an array

*

* @param a - first array of numbers

* @param b - second array of numbers

* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b

* should be sorted first

*/

function compareScoreArray(a, b) {

let i = 0;

while (i < a.length && i < b.length) {

const diff = b[i] - a[i];

// only keep going if diff === 0

if (diff)

return diff;

i++;

}

// if the last subsegment was Static, the shorter segments should be sorted first

// otherwise sort the longest segment first

if (a.length < b.length) {

return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */

? -1

: 1;

}

else if (a.length > b.length) {

return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */

? 1

: -1;

}

return 0;

}

/**

* Compare function that can be used with `sort` to sort an array of PathParser

*

* @param a - first PathParser

* @param b - second PathParser

* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b

*/

function comparePathParserScore(a, b) {

let i = 0;

const aScore = a.score;

const bScore = b.score;

while (i < aScore.length && i < bScore.length) {

const comp = compareScoreArray(aScore[i], bScore[i]);

// do not return if both are equal

if (comp)

return comp;

i++;

}

// if a and b share the same score entries but b has more, sort b first

return bScore.length - aScore.length;

// this is the ternary version

// return aScore.length < bScore.length

// ? 1

// : aScore.length > bScore.length

// ? -1

// : 0

}

const ROOT_TOKEN = {

type: 0 /* Static */,

value: '',

};

const VALID_PARAM_RE = /[a-zA-Z0-9_]/;

// After some profiling, the cache seems to be unnecessary because tokenizePath

// (the slowest part of adding a route) is very fast

// const tokenCache = new Map()

function tokenizePath(path) {

if (!path)

return [[]];

if (path === '/')

return [[ROOT_TOKEN]];

if (!path.startsWith('/')) {

throw new Error(`Route paths should start with a "/": "${path}" should be "/${path}".`

);

}

// if (tokenCache.has(path)) return tokenCache.get(path)!

function crash(message) {

throw new Error(`ERR (${state})/"${buffer}": ${message}`);

}

let state = 0 /* Static */;

let previousState = state;

const tokens = [];

// the segment will always be valid because we get into the initial state

// with the leading /

let segment;

function finalizeSegment() {

if (segment)

tokens.push(segment);

segment = [];

}

// index on the path

let i = 0;

// char at index

let char;

// buffer of the value read

let buffer = '';

// custom regexp for a param

let customRe = '';

function consumeBuffer() {

if (!buffer)

return;

if (state === 0 /* Static */) {

segment.push({

type: 0 /* Static */,

value: buffer,

});

}

else if (state === 1 /* Param */ ||

state === 2 /* ParamRegExp */ ||

state === 3 /* ParamRegExpEnd */) {

if (segment.length > 1 && (char === '*' || char === '+'))

crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);

segment.push({

type: 1 /* Param */,

value: buffer,

regexp: customRe,

repeatable: char === '*' || char === '+',

optional: char === '*' || char === '?',

});

}

else {

crash('Invalid state to consume buffer');

}

buffer = '';

}

function addCharToBuffer() {

buffer += char;

}

while (i < path.length) {

char = path[i++];

if (char === '\\' && state !== 2 /* ParamRegExp */) {

previousState = state;

state = 4 /* EscapeNext */;

continue;

}

switch (state) {

case 0 /* Static */:

if (char === '/') {

if (buffer) {

consumeBuffer();

}

finalizeSegment();

}

else if (char === ':') {

consumeBuffer();

state = 1 /* Param */;

}

else {

addCharToBuffer();

}

break;

case 4 /* EscapeNext */:

addCharToBuffer();

state = previousState;

break;

case 1 /* Param */:

if (char === '(') {

state = 2 /* ParamRegExp */;

}

else if (VALID_PARAM_RE.test(char)) {

addCharToBuffer();

}

else {

consumeBuffer();

state = 0 /* Static */;

// go back one character if we were not modifying

if (char !== '*' && char !== '?' && char !== '+')

i--;

}

break;

case 2 /* ParamRegExp */:

// TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)

// it already works by escaping the closing )

// https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#

// is this really something people need since you can also write

// /prefix_:p()_suffix

if (char === ')') {

// handle the escaped )

if (customRe[customRe.length - 1] == '\\')

customRe = customRe.slice(0, -1) + char;

else

state = 3 /* ParamRegExpEnd */;

}

else {

customRe += char;

}

break;

case 3 /* ParamRegExpEnd */:

// same as finalizing a param

consumeBuffer();

state = 0 /* Static */;

// go back one character if we were not modifying

if (char !== '*' && char !== '?' && char !== '+')

i--;

customRe = '';

break;

default:

crash('Unknown state');

break;

}

}

if (state === 2 /* ParamRegExp */)

crash(`Unfinished custom RegExp for param "${buffer}"`);

consumeBuffer();

finalizeSegment();

// tokenCache.set(path, tokens)

return tokens;

}

function createRouteRecordMatcher(record, parent, options) {

const parser = tokensToParser(tokenizePath(record.path), options);

// warn against params with the same name

{

const existingKeys = new Set();

for (const key of parser.keys) {

if (existingKeys.has(key.name))

warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`);

existingKeys.add(key.name);

}

}

const matcher = assign(parser, {

record,

parent,

// these needs to be populated by the parent

children: [],

alias: [],

});

if (parent) {

// both are aliases or both are not aliases

// we don't want to mix them because the order is used when

// passing originalRecord in Matcher.addRoute

if (!matcher.record.aliasOf === !parent.record.aliasOf)

parent.children.push(matcher);

}

return matcher;

}

/**

* Creates a Router Matcher.

*

* @internal

* @param routes - array of initial routes

* @param globalOptions - global route options

*/

function createRouterMatcher(routes, globalOptions) {

// normalized ordered array of matchers

const matchers = [];

const matcherMap = new Map();

globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);

function getRecordMatcher(name) {

return matcherMap.get(name);

}

function addRoute(record, parent, originalRecord) {

// used later on to remove by name

const isRootAdd = !originalRecord;

const mainNormalizedRecord = normalizeRouteRecord(record);

// we might be the child of an alias

mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;

const options = mergeOptions(globalOptions, record);

// generate an array of records to correctly handle aliases

const normalizedRecords = [

mainNormalizedRecord,

];

if ('alias' in record) {

const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;

for (const alias of aliases) {

normalizedRecords.push(assign({}, mainNormalizedRecord, {

// this allows us to hold a copy of the `components` option

// so that async components cache is hold on the original record

components: originalRecord

? originalRecord.record.components

: mainNormalizedRecord.components,

path: alias,

// we might be the child of an alias

aliasOf: originalRecord

? originalRecord.record

: mainNormalizedRecord,

// the aliases are always of the same kind as the original since they

// are defined on the same record

}));

}

}

let matcher;

let originalMatcher;

for (const normalizedRecord of normalizedRecords) {

const { path } = normalizedRecord;

// Build up the path for nested routes if the child isn't an absolute

// route. Only add the / delimiter if the child path isn't empty and if the

// parent path doesn't have a trailing slash

if (parent && path[0] !== '/') {

const parentPath = parent.record.path;

const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';

normalizedRecord.path =

parent.record.path + (path && connectingSlash + path);

}

if (normalizedRecord.path === '*') {

throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +

'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.');

}

// create the object before hand so it can be passed to children

matcher = createRouteRecordMatcher(normalizedRecord, parent, options);

if (parent && path[0] === '/')

checkMissingParamsInAbsolutePath(matcher, parent);

// if we are an alias we must tell the original record that we exist

// so we can be removed

if (originalRecord) {

originalRecord.alias.push(matcher);

{

checkSameParams(originalRecord, matcher);

}

}

else {

// otherwise, the first record is the original and others are aliases

originalMatcher = originalMatcher || matcher;

if (originalMatcher !== matcher)

originalMatcher.alias.push(matcher);

// remove the route if named and only for the top record (avoid in nested calls)

// this works because the original record is the first one

if (isRootAdd && record.name && !isAliasRecord(matcher))

removeRoute(record.name);

}

if ('children' in mainNormalizedRecord) {

const children = mainNormalizedRecord.children;

for (let i = 0; i < children.length; i++) {

addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);

}

}

// if there was no original record, then the first one was not an alias and all

// other alias (if any) need to reference this record when adding children

originalRecord = originalRecord || matcher;

// TODO: add normalized records for more flexibility

// if (parent && isAliasRecord(originalRecord)) {

// parent.children.push(originalRecord)

// }

insertMatcher(matcher);

}

return originalMatcher

? () => {

// since other matchers are aliases, they should be removed by the original matcher

removeRoute(originalMatcher);

}

: noop;

}

function removeRoute(matcherRef) {

if (isRouteName(matcherRef)) {

const matcher = matcherMap.get(matcherRef);

if (matcher) {

matcherMap.delete(matcherRef);

matchers.splice(matchers.indexOf(matcher), 1);

matcher.children.forEach(removeRoute);

matcher.alias.forEach(removeRoute);

}

}

else {

const index = matchers.indexOf(matcherRef);

if (index > -1) {

matchers.splice(index, 1);

if (matcherRef.record.name)

matcherMap.delete(matcherRef.record.name);

matcherRef.children.forEach(removeRoute);

matcherRef.alias.forEach(removeRoute);

}

}

}

function getRoutes() {

return matchers;

}

function insertMatcher(matcher) {

let i = 0;

while (i < matchers.length &&

comparePathParserScore(matcher, matchers[i]) >= 0 &&

// Adding children with empty path should still appear before the parent

// https://github.com/vuejs/router/issues/1124

(matcher.record.path !== matchers[i].record.path ||

!isRecordChildOf(matcher, matchers[i])))

i++;

matchers.splice(i, 0, matcher);

// only add the original record to the name map

if (matcher.record.name && !isAliasRecord(matcher))

matcherMap.set(matcher.record.name, matcher);

}

function resolve(location, currentLocation) {

let matcher;

let params = {};

let path;

let name;

if ('name' in location && location.name) {

matcher = matcherMap.get(location.name);

if (!matcher)

throw createRouterError(1 /* MATCHER_NOT_FOUND */, {

location,

});

name = matcher.record.name;

params = assign(

// paramsFromLocation is a new object

paramsFromLocation(currentLocation.params,

// only keep params that exist in the resolved location

// TODO: only keep optional params coming from a parent record

matcher.keys.filter(k => !k.optional).map(k => k.name)), location.params);

// throws if cannot be stringified

path = matcher.stringify(params);

}

else if ('path' in location) {

// no need to resolve the path with the matcher as it was provided

// this also allows the user to control the encoding

path = location.path;

if (!path.startsWith('/')) {

warn(`The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/router.`);

}

matcher = matchers.find(m => m.re.test(path));

// matcher should have a value after the loop

if (matcher) {

// TODO: dev warning of unused params if provided

// we know the matcher works because we tested the regexp

params = matcher.parse(path);

name = matcher.record.name;

}

// location is a relative path

}

else {

// match by name or path of current route

matcher = currentLocation.name

? matcherMap.get(currentLocation.name)

: matchers.find(m => m.re.test(currentLocation.path));

if (!matcher)

throw createRouterError(1 /* MATCHER_NOT_FOUND */, {

location,

currentLocation,

});

name = matcher.record.name;

// since we are navigating to the same location, we don't need to pick the

// params like when `name` is provided

params = assign({}, currentLocation.params, location.params);

path = matcher.stringify(params);

}

const matched = [];

let parentMatcher = matcher;

while (parentMatcher) {

// reversed order so parents are at the beginning

matched.unshift(parentMatcher.record);

parentMatcher = parentMatcher.parent;

}

return {

name,

path,

params,

matched,

meta: mergeMetaFields(matched),

};

}

// add initial routes

routes.forEach(route => addRoute(route));

return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher };

}

function paramsFromLocation(params, keys) {

const newParams = {};

for (const key of keys) {

if (key in params)

newParams[key] = params[key];

}

return newParams;

}

/**

* Normalizes a RouteRecordRaw. Creates a copy

*

* @param record

* @returns the normalized version

*/

function normalizeRouteRecord(record) {

return {

path: record.path,

redirect: record.redirect,

name: record.name,

meta: record.meta || {},

aliasOf: undefined,

beforeEnter: record.beforeEnter,

props: normalizeRecordProps(record),

children: record.children || [],

instances: {},

leaveGuards: new Set(),

updateGuards: new Set(),

enterCallbacks: {},

components: 'components' in record

? record.components || {}

: { default: record.component },

};

}

/**

* Normalize the optional `props` in a record to always be an object similar to

* components. Also accept a boolean for components.

* @param record

*/

function normalizeRecordProps(record) {

const propsObject = {};

// props does not exist on redirect records but we can set false directly

const props = record.props || false;

if ('component' in record) {

propsObject.default = props;

}

else {

// NOTE: we could also allow a function to be applied to every component.

// Would need user feedback for use cases

for (const name in record.components)

propsObject[name] = typeof props === 'boolean' ? props : props[name];

}

return propsObject;

}

/**

* Checks if a record or any of its parent is an alias

* @param record

*/

function isAliasRecord(record) {

while (record) {

if (record.record.aliasOf)

return true;

record = record.parent;

}

return false;

}

/**

* Merge meta fields of an array of records

*

* @param matched - array of matched records

*/

function mergeMetaFields(matched) {

return matched.reduce((meta, record) => assign(meta, record.meta), {});

}

function mergeOptions(defaults, partialOptions) {

const options = {};

for (const key in defaults) {

options[key] = key in partialOptions ? partialOptions[key] : defaults[key];

}

return options;

}

function isSameParam(a, b) {

return (a.name === b.name &&

a.optional === b.optional &&

a.repeatable === b.repeatable);

}

/**

* Check if a path and its alias have the same required params

*

* @param a - original record

* @param b - alias record

*/

function checkSameParams(a, b) {

for (const key of a.keys) {

if (!key.optional && !b.keys.find(isSameParam.bind(null, key)))

return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);

}

for (const key of b.keys) {

if (!key.optional && !a.keys.find(isSameParam.bind(null, key)))

return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);

}

}

function checkMissingParamsInAbsolutePath(record, parent) {

for (const key of parent.keys) {

if (!record.keys.find(isSameParam.bind(null, key)))

return warn(`Absolute path "${record.record.path}" should have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);

}

}

function isRecordChildOf(record, parent) {

return parent.children.some(child => child === record || isRecordChildOf(record, child));

}

/**

* Encoding Rules ␣ = Space Path: ␣ " < > # ? { } Query: ␣ " < > # & = Hash: ␣ "

* < > `

*

* On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)

* defines some extra characters to be encoded. Most browsers do not encode them

* in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to

* also encode `!'()*`. Leaving unencoded only ASCII alphanumeric(`a-zA-Z0-9`)

* plus `-._~`. This extra safety should be applied to query by patching the

* string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`

* should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`

* into a `/` if directly typed in. The _backtick_ (`````) should also be

* encoded everywhere because some browsers like FF encode it when directly

* written while others don't. Safari and IE don't encode ``"<>{}``` in hash.

*/

// const EXTRA_RESERVED_RE = /[!'()*]/g

// const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)

const HASH_RE = /#/g; // %23

const AMPERSAND_RE = /&/g; // %26

const SLASH_RE = /\//g; // %2F

const EQUAL_RE = /=/g; // %3D

const IM_RE = /\?/g; // %3F

const PLUS_RE = /\+/g; // %2B

/**

* NOTE: It's not clear to me if we should encode the + symbol in queries, it

* seems to be less flexible than not doing so and I can't find out the legacy

* systems requiring this for regular requests like text/html. In the standard,

* the encoding of the plus character is only mentioned for

* application/x-www-form-urlencoded

* (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo

* leave the plus character as is in queries. To be more flexible, we allow the

* plus character on the query but it can also be manually encoded by the user.

*

* Resources:

* - https://url.spec.whatwg.org/#urlencoded-parsing

* - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20

*/

const ENC_BRACKET_OPEN_RE = /%5B/g; // [

const ENC_BRACKET_CLOSE_RE = /%5D/g; // ]

const ENC_CARET_RE = /%5E/g; // ^

const ENC_BACKTICK_RE = /%60/g; // `

const ENC_CURLY_OPEN_RE = /%7B/g; // {

const ENC_PIPE_RE = /%7C/g; // |

const ENC_CURLY_CLOSE_RE = /%7D/g; // }

const ENC_SPACE_RE = /%20/g; // }

/**

* Encode characters that need to be encoded on the path, search and hash

* sections of the URL.

*

* @internal

* @param text - string to encode

* @returns encoded string

*/

function commonEncode(text) {

return encodeURI('' + text)

.replace(ENC_PIPE_RE, '|')

.replace(ENC_BRACKET_OPEN_RE, '[')

.replace(ENC_BRACKET_CLOSE_RE, ']');

}

/**

* Encode characters that need to be encoded on the hash section of the URL.

*

* @param text - string to encode

* @returns encoded string

*/

function encodeHash(text) {

return commonEncode(text)

.replace(ENC_CURLY_OPEN_RE, '{')

.replace(ENC_CURLY_CLOSE_RE, '}')

.replace(ENC_CARET_RE, '^');

}

/**

* Encode characters that need to be encoded query values on the query

* section of the URL.

*

* @param text - string to encode

* @returns encoded string

*/

function encodeQueryValue(text) {

return (commonEncode(text)

// Encode the space as +, encode the + to differentiate it from the space

.replace(PLUS_RE, '%2B')

.replace(ENC_SPACE_RE, '+')

.replace(HASH_RE, '%23')

.replace(AMPERSAND_RE, '%26')

.replace(ENC_BACKTICK_RE, '`')

.replace(ENC_CURLY_OPEN_RE, '{')

.replace(ENC_CURLY_CLOSE_RE, '}')

.replace(ENC_CARET_RE, '^'));

}

/**

* Like `encodeQueryValue` but also encodes the `=` character.

*

* @param text - string to encode

*/

function encodeQueryKey(text) {

return encodeQueryValue(text).replace(EQUAL_RE, '%3D');

}

/**

* Encode characters that need to be encoded on the path section of the URL.

*

* @param text - string to encode

* @returns encoded string

*/

function encodePath(text) {

return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F');

}

/**

* Encode characters that need to be encoded on the path section of the URL as a

* param. This function encodes everything {@link encodePath} does plus the

* slash (`/`) character. If `text` is `null` or `undefined`, returns an empty

* string instead.

*

* @param text - string to encode

* @returns encoded string

*/

function encodeParam(text) {

return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F');

}

/**

* Decode text using `decodeURIComponent`. Returns the original text if it

* fails.

*

* @param text - string to decode

* @returns decoded string

*/

function decode(text) {

try {

return decodeURIComponent('' + text);

}

catch (err) {

warn(`Error decoding "${text}". Using original value`);

}

return '' + text;

}

/**

* Transforms a queryString into a {@link LocationQuery} object. Accept both, a

* version with the leading `?` and without Should work as URLSearchParams

* @internal

*

* @param search - search string to parse

* @returns a query object

*/

function parseQuery(search) {

const query = {};

// avoid creating an object with an empty key and empty value

// because of split('&')

if (search === '' || search === '?')

return query;

const hasLeadingIM = search[0] === '?';

const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');

for (let i = 0; i < searchParams.length; ++i) {

// pre decode the + into space

const searchParam = searchParams[i].replace(PLUS_RE, ' ');

// allow the = character

const eqPos = searchParam.indexOf('=');

const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));

const value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));

if (key in query) {

// an extra variable for ts types

let currentValue = query[key];

if (!Array.isArray(currentValue)) {

currentValue = query[key] = [currentValue];

}

currentValue.push(value);

}

else {

query[key] = value;

}

}

return query;

}

/**

* Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it

* doesn't prepend a `?`

*

* @internal

*

* @param query - query object to stringify

* @returns string version of the query without the leading `?`

*/

function stringifyQuery(query) {

let search = '';

for (let key in query) {

const value = query[key];

key = encodeQueryKey(key);

if (value == null) {

// only null adds the value

if (value !== undefined) {

search += (search.length ? '&' : '') + key;

}

continue;

}

// keep null values

const values = Array.isArray(value)

? value.map(v => v && encodeQueryValue(v))

: [value && encodeQueryValue(value)];

values.forEach(value => {

// skip undefined values in arrays as if they were not present

// smaller code than using filter

if (value !== undefined) {

// only append & with non-empty search

search += (search.length ? '&' : '') + key;

if (value != null)

search += '=' + value;

}

});

}

return search;

}

/**

* Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting

* numbers into strings, removing keys with an undefined value and replacing

* undefined with null in arrays

*

* @param query - query object to normalize

* @returns a normalized query object

*/

function normalizeQuery(query) {

const normalizedQuery = {};

for (const key in query) {

const value = query[key];

if (value !== undefined) {

normalizedQuery[key] = Array.isArray(value)

? value.map(v => (v == null ? null : '' + v))

: value == null

? value

: '' + value;

}

}

return normalizedQuery;

}

/**

* Create a list of callbacks that can be reset. Used to create before and after navigation guards list

*/

function useCallbacks() {

let handlers = [];

function add(handler) {

handlers.push(handler);

return () => {

const i = handlers.indexOf(handler);

if (i > -1)

handlers.splice(i, 1);

};

}

function reset() {

handlers = [];

}

return {

add,

list: () => handlers,

reset,

};

}

function registerGuard(record, name, guard) {

const removeFromList = () => {

record[name].delete(guard);

};

vue.onUnmounted(removeFromList);

vue.onDeactivated(removeFromList);

vue.onActivated(() => {

record[name].add(guard);

});

record[name].add(guard);

}

/**

* Add a navigation guard that triggers whenever the component for the current

* location is about to be left. Similar to {@link beforeRouteLeave} but can be

* used in any component. The guard is removed when the component is unmounted.

*

* @param leaveGuard - {@link NavigationGuard}

*/

function onBeforeRouteLeave(leaveGuard) {

if (!vue.getCurrentInstance()) {

warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function');

return;

}

const activeRecord = vue.inject(matchedRouteKey,

// to avoid warning

{}).value;

if (!activeRecord) {

warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside of a component child of . Maybe you called it inside of App.vue?');

return;

}

registerGuard(activeRecord, 'leaveGuards', leaveGuard);

}

/**

* Add a navigation guard that triggers whenever the current location is about

* to be updated. Similar to {@link beforeRouteUpdate} but can be used in any

* component. The guard is removed when the component is unmounted.

*

* @param updateGuard - {@link NavigationGuard}

*/

function onBeforeRouteUpdate(updateGuard) {

if (!vue.getCurrentInstance()) {

warn('getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function');

return;

}

const activeRecord = vue.inject(matchedRouteKey,

// to avoid warning

{}).value;

if (!activeRecord) {

warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside of a component child of . Maybe you called it inside of App.vue?');

return;

}

registerGuard(activeRecord, 'updateGuards', updateGuard);

}

function guardToPromiseFn(guard, to, from, record, name) {

// keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place

const enterCallbackArray = record &&

// name is defined if record is because of the function overload

(record.enterCallbacks[name] = record.enterCallbacks[name] || []);

return () => new Promise((resolve, reject) => {

const next = (valid) => {

if (valid === false)

reject(createRouterError(4 /* NAVIGATION_ABORTED */, {

from,

to,

}));

else if (valid instanceof Error) {

reject(valid);

}

else if (isRouteLocation(valid)) {

reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, {

from: to,

to: valid,

}));

}

else {

if (enterCallbackArray &&

// since enterCallbackArray is truthy, both record and name also are

record.enterCallbacks[name] === enterCallbackArray &&

typeof valid === 'function')

enterCallbackArray.push(valid);

resolve();

}

};

// wrapping with Promise.resolve allows it to work with both async and sync guards

const guardReturn = guard.call(record && record.instances[name], to, from, canOnlyBeCalledOnce(next, to, from) );

let guardCall = Promise.resolve(guardReturn);

if (guard.length < 3)

guardCall = guardCall.then(next);

if (guard.length > 2) {

const message = `The "next" callback was never called inside of ${guard.name ? '"' + guard.name + '"' : ''}:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.`;

if (typeof guardReturn === 'object' && 'then' in guardReturn) {

guardCall = guardCall.then(resolvedValue => {

// @ts-expect-error: _called is added at canOnlyBeCalledOnce

if (!next._called) {

warn(message);

return Promise.reject(new Error('Invalid navigation guard'));

}

return resolvedValue;

});

// TODO: test me!

}

else if (guardReturn !== undefined) {

// @ts-expect-error: _called is added at canOnlyBeCalledOnce

if (!next._called) {

warn(message);

reject(new Error('Invalid navigation guard'));

return;

}

}

}

guardCall.catch(err => reject(err));

});

}

function canOnlyBeCalledOnce(next, to, from) {

let called = 0;

return function () {

if (called++ === 1)

warn(`The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.`);

// @ts-expect-error: we put it in the original one because it's easier to check

next._called = true;

if (called === 1)

next.apply(null, arguments);

};

}

function extractComponentsGuards(matched, guardType, to, from) {

const guards = [];

for (const record of matched) {

for (const name in record.components) {

let rawComponent = record.components[name];

{

if (!rawComponent ||

(typeof rawComponent !== 'object' &&

typeof rawComponent !== 'function')) {

warn(`Component "${name}" in record with path "${record.path}" is not` +

` a valid component. Received "${String(rawComponent)}".`);

// throw to ensure we stop here but warn to ensure the message isn't

// missed by the user

throw new Error('Invalid route component');

}

else if ('then' in rawComponent) {

// warn if user wrote import('/component.vue') instead of () =>

// import('./component.vue')

warn(`Component "${name}" in record with path "${record.path}" is a ` +

`Promise instead of a function that returns a Promise. Did you ` +

`write "import('./MyPage.vue')" instead of ` +

`"() => import('./MyPage.vue')" ? This will break in ` +

`production if not fixed.`);

const promise = rawComponent;

rawComponent = () => promise;

}

else if (rawComponent.__asyncLoader &&

// warn only once per component

!rawComponent.__warnedDefineAsync) {

rawComponent.__warnedDefineAsync = true;

warn(`Component "${name}" in record with path "${record.path}" is defined ` +

`using "defineAsyncComponent()". ` +

`Write "() => import('./MyPage.vue')" instead of ` +

`"defineAsyncComponent(() => import('./MyPage.vue'))".`);

}

}

// skip update and leave guards if the route component is not mounted

if (guardType !== 'beforeRouteEnter' && !record.instances[name])

continue;

if (isRouteComponent(rawComponent)) {

// __vccOpts is added by vue-class-component and contain the regular options

const options = rawComponent.__vccOpts || rawComponent;

const guard = options[guardType];

guard && guards.push(guardToPromiseFn(guard, to, from, record, name));

}

else {

// start requesting the chunk already

let componentPromise = rawComponent();

if (!('catch' in componentPromise)) {

warn(`Component "${name}" in record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`);

componentPromise = Promise.resolve(componentPromise);

}

guards.push(() => componentPromise.then(resolved => {

if (!resolved)

return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}"`));

const resolvedComponent = isESModule(resolved)

? resolved.default

: resolved;

// replace the function with the resolved component

record.components[name] = resolvedComponent;

// __vccOpts is added by vue-class-component and contain the regular options

const options = resolvedComponent.__vccOpts || resolvedComponent;

const guard = options[guardType];

return guard && guardToPromiseFn(guard, to, from, record, name)();

}));

}

}

}

return guards;

}

/**

* Allows differentiating lazy components from functional components and vue-class-component

*

* @param component

*/

function isRouteComponent(component) {

return (typeof component === 'object' ||

'displayName' in component ||

'props' in component ||

'__vccOpts' in component);

}

// TODO: we could allow currentRoute as a prop to expose `isActive` and

// `isExactActive` behavior should go through an RFC

function useLink(props) {

const router = vue.inject(routerKey);

const currentRoute = vue.inject(routeLocationKey);

const route = vue.computed(() => router.resolve(vue.unref(props.to)));

const activeRecordIndex = vue.computed(() => {

const { matched } = route.value;

const { length } = matched;

const routeMatched = matched[length - 1];

const currentMatched = currentRoute.matched;

if (!routeMatched || !currentMatched.length)

return -1;

const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));

if (index > -1)

return index;

// possible parent record

const parentRecordPath = getOriginalPath(matched[length - 2]);

return (

// we are dealing with nested routes

length > 1 &&

// if the parent and matched route have the same path, this link is

// referring to the empty child. Or we currently are on a different

// child of the same parent

getOriginalPath(routeMatched) === parentRecordPath &&

// avoid comparing the child with its parent

currentMatched[currentMatched.length - 1].path !== parentRecordPath

? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))

: index);

});

const isActive = vue.computed(() => activeRecordIndex.value > -1 &&

includesParams(currentRoute.params, route.value.params));

const isExactActive = vue.computed(() => activeRecordIndex.value > -1 &&

activeRecordIndex.value === currentRoute.matched.length - 1 &&

isSameRouteLocationParams(currentRoute.params, route.value.params));

function navigate(e = {}) {

if (guardEvent(e)) {

return router[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to)

// avoid uncaught errors are they are logged anyway

).catch(noop);

}

return Promise.resolve();

}

// devtools only

if (isBrowser) {

const instance = vue.getCurrentInstance();

if (instance) {

const linkContextDevtools = {

route: route.value,

isActive: isActive.value,

isExactActive: isExactActive.value,

};

// @ts-expect-error: this is internal

instance.__vrl_devtools = instance.__vrl_devtools || [];

// @ts-expect-error: this is internal

instance.__vrl_devtools.push(linkContextDevtools);

vue.watchEffect(() => {

linkContextDevtools.route = route.value;

linkContextDevtools.isActive = isActive.value;

linkContextDevtools.isExactActive = isExactActive.value;

}, { flush: 'post' });

}

}

return {

route,

href: vue.computed(() => route.value.href),

isActive,

isExactActive,

navigate,

};

}

const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({

name: 'RouterLink',

props: {

to: {

type: [String, Object],

required: true,

},

replace: Boolean,

activeClass: String,

// inactiveClass: String,

exactActiveClass: String,

custom: Boolean,

ariaCurrentValue: {

type: String,

default: 'page',

},

},

useLink,

setup(props, { slots }) {

const link = vue.reactive(useLink(props));

const { options } = vue.inject(routerKey);

const elClass = vue.computed(() => ({

[getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,

// [getLinkClass(

// props.inactiveClass,

// options.linkInactiveClass,

// 'router-link-inactive'

// )]: !link.isExactActive,

[getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,

}));

return () => {

const children = slots.default && slots.default(link);

return props.custom

? children

: vue.h('a', {

'aria-current': link.isExactActive

? props.ariaCurrentValue

: null,

href: link.href,

// this would override user added attrs but Vue will still add

// the listener so we end up triggering both

onClick: link.navigate,

class: elClass.value,

}, children);

};

},

});

// export the public type for h/tsx inference

// also to avoid inline import() in generated d.ts files

/**

* Component to render a link that triggers a navigation on click.

*/

const RouterLink = RouterLinkImpl;

function guardEvent(e) {

// don't redirect with control keys

if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)

return;

// don't redirect when preventDefault called

if (e.defaultPrevented)

return;

// don't redirect on right click

if (e.button !== undefined && e.button !== 0)

return;

// don't redirect if `target="_blank"`

// @ts-expect-error getAttribute does exist

if (e.currentTarget && e.currentTarget.getAttribute) {

// @ts-expect-error getAttribute exists

const target = e.currentTarget.getAttribute('target');

if (/\b_blank\b/i.test(target))

return;

}

// this may be a Weex event which doesn't have this method

if (e.preventDefault)

e.preventDefault();

return true;

}

function includesParams(outer, inner) {

for (const key in inner) {

const innerValue = inner[key];

const outerValue = outer[key];

if (typeof innerValue === 'string') {

if (innerValue !== outerValue)

return false;

}

else {

if (!Array.isArray(outerValue) ||

outerValue.length !== innerValue.length ||

innerValue.some((value, i) => value !== outerValue[i]))

return false;

}

}

return true;

}

/**

* Get the original path value of a record by following its aliasOf

* @param record

*/

function getOriginalPath(record) {

return record ? (record.aliasOf ? record.aliasOf.path : record.path) : '';

}

/**

* Utility class to get the active class based on defaults.

* @param propClass

* @param globalClass

* @param defaultClass

*/

const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null

? propClass

: globalClass != null

? globalClass

: defaultClass;

const RouterViewImpl = /*#__PURE__*/ vue.defineComponent({

name: 'RouterView',

// #674 we manually inherit them

inheritAttrs: false,

props: {

name: {

type: String,

default: 'default',

},

route: Object,

},

setup(props, { attrs, slots }) {

warnDeprecatedUsage();

const injectedRoute = vue.inject(routerViewLocationKey);

const routeToDisplay = vue.computed(() => props.route || injectedRoute.value);

const depth = vue.inject(viewDepthKey, 0);

const matchedRouteRef = vue.computed(() => routeToDisplay.value.matched[depth]);

vue.provide(viewDepthKey, depth + 1);

vue.provide(matchedRouteKey, matchedRouteRef);

vue.provide(routerViewLocationKey, routeToDisplay);

const viewRef = vue.ref();

// watch at the same time the component instance, the route record we are

// rendering, and the name

vue.watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {

// copy reused instances

if (to) {

// this will update the instance for new instances as well as reused

// instances when navigating to a new route

to.instances[name] = instance;

// the component instance is reused for a different route or name so

// we copy any saved update or leave guards. With async setup, the

// mounting component will mount before the matchedRoute changes,

// making instance === oldInstance, so we check if guards have been

// added before. This works because we remove guards when

// unmounting/deactivating components

if (from && from !== to && instance && instance === oldInstance) {

if (!to.leaveGuards.size) {

to.leaveGuards = from.leaveGuards;

}

if (!to.updateGuards.size) {

to.updateGuards = from.updateGuards;

}

}

}

// trigger beforeRouteEnter next callbacks

if (instance &&

to &&

// if there is no instance but to and from are the same this might be

// the first visit

(!from || !isSameRouteRecord(to, from) || !oldInstance)) {

(to.enterCallbacks[name] || []).forEach(callback => callback(instance));

}

}, { flush: 'post' });

return () => {

const route = routeToDisplay.value;

const matchedRoute = matchedRouteRef.value;

const ViewComponent = matchedRoute && matchedRoute.components[props.name];

// we need the value at the time we render because when we unmount, we

// navigated to a different location so the value is different

const currentName = props.name;

if (!ViewComponent) {

return normalizeSlot(slots.default, { Component: ViewComponent, route });

}

// props from route configuration

const routePropsOption = matchedRoute.props[props.name];

const routeProps = routePropsOption

? routePropsOption === true

? route.params

: typeof routePropsOption === 'function'

? routePropsOption(route)

: routePropsOption

: null;

const onVnodeUnmounted = vnode => {

// remove the instance reference to prevent leak

if (vnode.component.isUnmounted) {

matchedRoute.instances[currentName] = null;

}

};

const component = vue.h(ViewComponent, assign({}, routeProps, attrs, {

onVnodeUnmounted,

ref: viewRef,

}));

if (isBrowser &&

component.ref) {

// TODO: can display if it's an alias, its props

const info = {

depth,

name: matchedRoute.name,

path: matchedRoute.path,

meta: matchedRoute.meta,

};

const internalInstances = Array.isArray(component.ref)

? component.ref.map(r => r.i)

: [component.ref.i];

internalInstances.forEach(instance => {

// @ts-expect-error

instance.__vrv_devtools = info;

});

}

return (

// pass the vnode to the slot as a prop.

// h and both accept vnodes

normalizeSlot(slots.default, { Component: component, route }) ||

component);

};

},

});

function normalizeSlot(slot, data) {

if (!slot)

return null;

const slotContent = slot(data);

return slotContent.length === 1 ? slotContent[0] : slotContent;

}

// export the public type for h/tsx inference

// also to avoid inline import() in generated d.ts files

/**

* Component to display the current route the user is at.

*/

const RouterView = RouterViewImpl;

// warn against deprecated usage with &

// due to functional component being no longer eager in Vue 3

function warnDeprecatedUsage() {

const instance = vue.getCurrentInstance();

const parentName = instance.parent && instance.parent.type.name;

if (parentName &&

(parentName === 'KeepAlive' || parentName.includes('Transition'))) {

const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';

warn(` can no longer be used directly inside or .\n` +

`Use slot props instead:\n\n` +

`\n` +

` <${comp}>\n` +

` \n` +

` \n` +

``);

}

}

function getDevtoolsGlobalHook() {

return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;

}

function getTarget() {

// @ts-ignore

return (typeof navigator !== 'undefined' && typeof window !== 'undefined')

? window

: typeof global !== 'undefined'

? global

: {};

}

const isProxyAvailable = typeof Proxy === 'function';

const HOOK_SETUP = 'devtools-plugin:setup';

const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set';

class ApiProxy {

constructor(plugin, hook) {

this.target = null;

this.targetQueue = [];

this.onQueue = [];

this.plugin = plugin;

this.hook = hook;

const defaultSettings = {};

if (plugin.settings) {

for (const id in plugin.settings) {

const item = plugin.settings[id];

defaultSettings[id] = item.defaultValue;

}

}

const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;

let currentSettings = Object.assign({}, defaultSettings);

try {

const raw = localStorage.getItem(localSettingsSaveId);

const data = JSON.parse(raw);

Object.assign(currentSettings, data);

}

catch (e) {

// noop

}

this.fallbacks = {

getSettings() {

return currentSettings;

},

setSettings(value) {

try {

localStorage.setItem(localSettingsSaveId, JSON.stringify(value));

}

catch (e) {

// noop

}

currentSettings = value;

},

};

if (hook) {

hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {

if (pluginId === this.plugin.id) {

this.fallbacks.setSettings(value);

}

});

}

this.proxiedOn = new Proxy({}, {

get: (_target, prop) => {

if (this.target) {

return this.target.on[prop];

}

else {

return (...args) => {

this.onQueue.push({

method: prop,

args,

});

};

}

},

});

this.proxiedTarget = new Proxy({}, {

get: (_target, prop) => {

if (this.target) {

return this.target[prop];

}

else if (prop === 'on') {

return this.proxiedOn;

}

else if (Object.keys(this.fallbacks).includes(prop)) {

return (...args) => {

this.targetQueue.push({

method: prop,

args,

resolve: () => { },

});

return this.fallbacks[prop](...args);

};

}

else {

return (...args) => {

return new Promise(resolve => {

this.targetQueue.push({

method: prop,

args,

resolve,

});

});

};

}

},

});

}

async setRealTarget(target) {

this.target = target;

for (const item of this.onQueue) {

this.target.on[item.method](...item.args);

}

for (const item of this.targetQueue) {

item.resolve(await this.target[item.method](...item.args));

}

}

}

function setupDevtoolsPlugin(pluginDescriptor, setupFn) {

const descriptor = pluginDescriptor;

const target = getTarget();

const hook = getDevtoolsGlobalHook();

const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;

if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {

hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);

}

else {

const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;

const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];

list.push({

pluginDescriptor: descriptor,

setupFn,

proxy,

});

if (proxy)

setupFn(proxy.proxiedTarget);

}

}

function formatRouteLocation(routeLocation, tooltip) {

const copy = assign({}, routeLocation, {

// remove variables that can contain vue instances

matched: routeLocation.matched.map(matched => omit(matched, ['instances', 'children', 'aliasOf'])),

});

return {

_custom: {

type: null,

readOnly: true,

display: routeLocation.fullPath,

tooltip,

value: copy,

},

};

}

function formatDisplay(display) {

return {

_custom: {

display,

},

};

}

// to support multiple router instances

let routerId = 0;

function addDevtools(app, router, matcher) {

// Take over router.beforeEach and afterEach

// make sure we are not registering the devtool twice

if (router.__hasDevtools)

return;

router.__hasDevtools = true;

// increment to support multiple router instances

const id = routerId++;

setupDevtoolsPlugin({

id: 'org.vuejs.router' + (id ? '.' + id : ''),

label: 'Vue Router',

packageName: 'vue-router',

homepage: 'https://router.vuejs.org',

logo: 'https://router.vuejs.org/logo.png',

componentStateTypes: ['Routing'],

app,

}, api => {

// display state added by the router

api.on.inspectComponent((payload, ctx) => {

if (payload.instanceData) {

payload.instanceData.state.push({

type: 'Routing',

key: '$route',

editable: false,

value: formatRouteLocation(router.currentRoute.value, 'Current Route'),

});

}

});

// mark router-link as active and display tags on router views

api.on.visitComponentTree(({ treeNode: node, componentInstance }) => {

if (componentInstance.__vrv_devtools) {

const info = componentInstance.__vrv_devtools;

node.tags.push({

label: (info.name ? `${info.name.toString()}: ` : '') + info.path,

textColor: 0,

tooltip: 'This component is rendered by <router-view>',

backgroundColor: PINK_500,

});

}

// if multiple useLink are used

if (Array.isArray(componentInstance.__vrl_devtools)) {

componentInstance.__devtoolsApi = api;

componentInstance.__vrl_devtools.forEach(devtoolsData => {

let backgroundColor = ORANGE_400;

let tooltip = '';

if (devtoolsData.isExactActive) {

backgroundColor = LIME_500;

tooltip = 'This is exactly active';

}

else if (devtoolsData.isActive) {

backgroundColor = BLUE_600;

tooltip = 'This link is active';

}

node.tags.push({

label: devtoolsData.route.path,

textColor: 0,

tooltip,

backgroundColor,

});

});

}

});

vue.watch(router.currentRoute, () => {

// refresh active state

refreshRoutesView();

api.notifyComponentUpdate();

api.sendInspectorTree(routerInspectorId);

api.sendInspectorState(routerInspectorId);

});

const navigationsLayerId = 'router:navigations:' + id;

api.addTimelineLayer({

id: navigationsLayerId,

label: `Router${id ? ' ' + id : ''} Navigations`,

color: 0x40a8c4,

});

// const errorsLayerId = 'router:errors'

// api.addTimelineLayer({

// id: errorsLayerId,

// label: 'Router Errors',

// color: 0xea5455,

// })

router.onError((error, to) => {

api.addTimelineEvent({

layerId: navigationsLayerId,

event: {

title: 'Error during Navigation',

subtitle: to.fullPath,

logType: 'error',

time: Date.now(),

data: { error },

groupId: to.meta.__navigationId,

},

});

});

// attached to `meta` and used to group events

let navigationId = 0;

router.beforeEach((to, from) => {

const data = {

guard: formatDisplay('beforeEach'),

from: formatRouteLocation(from, 'Current Location during this navigation'),

to: formatRouteLocation(to, 'Target location'),

};

// Used to group navigations together, hide from devtools

Object.defineProperty(to.meta, '__navigationId', {

value: navigationId++,

});

api.addTimelineEvent({

layerId: navigationsLayerId,

event: {

time: Date.now(),

title: 'Start of navigation',

subtitle: to.fullPath,

data,

groupId: to.meta.__navigationId,

},

});

});

router.afterEach((to, from, failure) => {

const data = {

guard: formatDisplay('afterEach'),

};

if (failure) {

data.failure = {

_custom: {

type: Error,

readOnly: true,

display: failure ? failure.message : '',

tooltip: 'Navigation Failure',

value: failure,

},

};

data.status = formatDisplay('❌');

}

else {

data.status = formatDisplay('✅');

}

// we set here to have the right order

data.from = formatRouteLocation(from, 'Current Location during this navigation');

data.to = formatRouteLocation(to, 'Target location');

api.addTimelineEvent({

layerId: navigationsLayerId,

event: {

title: 'End of navigation',

subtitle: to.fullPath,

time: Date.now(),

data,

logType: failure ? 'warning' : 'default',

groupId: to.meta.__navigationId,

},

});

});

/**

* Inspector of Existing routes

*/

const routerInspectorId = 'router-inspector:' + id;

api.addInspector({

id: routerInspectorId,

label: 'Routes' + (id ? ' ' + id : ''),

icon: 'book',

treeFilterPlaceholder: 'Search routes',

});

function refreshRoutesView() {

// the routes view isn't active

if (!activeRoutesPayload)

return;

const payload = activeRoutesPayload;

// children routes will appear as nested

let routes = matcher.getRoutes().filter(route => !route.parent);

// reset match state to false

routes.forEach(resetMatchStateOnRouteRecord);

// apply a match state if there is a payload

if (payload.filter) {

routes = routes.filter(route =>

// save matches state based on the payload

isRouteMatching(route, payload.filter.toLowerCase()));

}

// mark active routes

routes.forEach(route => markRouteRecordActive(route, router.currentRoute.value));

payload.rootNodes = routes.map(formatRouteRecordForInspector);

}

let activeRoutesPayload;

api.on.getInspectorTree(payload => {

activeRoutesPayload = payload;

if (payload.app === app && payload.inspectorId === routerInspectorId) {

refreshRoutesView();

}

});

/**

* Display information about the currently selected route record

*/

api.on.getInspectorState(payload => {

if (payload.app === app && payload.inspectorId === routerInspectorId) {

const routes = matcher.getRoutes();

const route = routes.find(route => route.record.__vd_id === payload.nodeId);

if (route) {

payload.state = {

options: formatRouteRecordMatcherForStateInspector(route),

};

}

}

});

api.sendInspectorTree(routerInspectorId);

api.sendInspectorState(routerInspectorId);

});

}

function modifierForKey(key) {

if (key.optional) {

return key.repeatable ? '*' : '?';

}

else {

return key.repeatable ? '+' : '';

}

}

function formatRouteRecordMatcherForStateInspector(route) {

const { record } = route;

const fields = [

{ editable: false, key: 'path', value: record.path },

];

if (record.name != null) {

fields.push({

editable: false,

key: 'name',

value: record.name,

});

}

fields.push({ editable: false, key: 'regexp', value: route.re });

if (route.keys.length) {

fields.push({

editable: false,

key: 'keys',

value: {

_custom: {

type: null,

readOnly: true,

display: route.keys

.map(key => `${key.name}${modifierForKey(key)}`)

.join(' '),

tooltip: 'Param keys',

value: route.keys,

},

},

});

}

if (record.redirect != null) {

fields.push({

editable: false,

key: 'redirect',

value: record.redirect,

});

}

if (route.alias.length) {

fields.push({

editable: false,

key: 'aliases',

value: route.alias.map(alias => alias.record.path),

});

}

fields.push({

key: 'score',

editable: false,

value: {

_custom: {

type: null,

readOnly: true,

display: route.score.map(score => score.join(', ')).join(' | '),

tooltip: 'Score used to sort routes',

value: route.score,

},

},

});

return fields;

}

/**

* Extracted from tailwind palette

*/

const PINK_500 = 0xec4899;

const BLUE_600 = 0x2563eb;

const LIME_500 = 0x84cc16;

const CYAN_400 = 0x22d3ee;

const ORANGE_400 = 0xfb923c;

// const GRAY_100 = 0xf4f4f5

const DARK = 0x666666;

function formatRouteRecordForInspector(route) {

const tags = [];

const { record } = route;

if (record.name != null) {

tags.push({

label: String(record.name),

textColor: 0,

backgroundColor: CYAN_400,

});

}

if (record.aliasOf) {

tags.push({

label: 'alias',

textColor: 0,

backgroundColor: ORANGE_400,

});

}

if (route.__vd_match) {

tags.push({

label: 'matches',

textColor: 0,

backgroundColor: PINK_500,

});

}

if (route.__vd_exactActive) {

tags.push({

label: 'exact',

textColor: 0,

backgroundColor: LIME_500,

});

}

if (route.__vd_active) {

tags.push({

label: 'active',

textColor: 0,

backgroundColor: BLUE_600,

});

}

if (record.redirect) {

tags.push({

label: 'redirect: ' +

(typeof record.redirect === 'string' ? record.redirect : 'Object'),

textColor: 0xffffff,

backgroundColor: DARK,

});

}

// add an id to be able to select it. Using the `path` is not possible because

// empty path children would collide with their parents

let id = record.__vd_id;

if (id == null) {

id = String(routeRecordId++);

record.__vd_id = id;

}

return {

id,

label: record.path,

tags,

children: route.children.map(formatRouteRecordForInspector),

};

}

// incremental id for route records and inspector state

let routeRecordId = 0;

const EXTRACT_REGEXP_RE = /^\/(.*)\/([a-z]*)$/;

function markRouteRecordActive(route, currentRoute) {

// no route will be active if matched is empty

// reset the matching state

const isExactActive = currentRoute.matched.length &&

isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record);

route.__vd_exactActive = route.__vd_active = isExactActive;

if (!isExactActive) {

route.__vd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));

}

route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute));

}

function resetMatchStateOnRouteRecord(route) {

route.__vd_match = false;

route.children.forEach(resetMatchStateOnRouteRecord);

}

function isRouteMatching(route, filter) {

const found = String(route.re).match(EXTRACT_REGEXP_RE);

route.__vd_match = false;

if (!found || found.length < 3) {

return false;

}

// use a regexp without $ at the end to match nested routes better

const nonEndingRE = new RegExp(found[1].replace(/\$$/, ''), found[2]);

if (nonEndingRE.test(filter)) {

// mark children as matches

route.children.forEach(child => isRouteMatching(child, filter));

// exception case: `/`

if (route.record.path !== '/' || filter === '/') {

route.__vd_match = route.re.test(filter);

return true;

}

// hide the / route

return false;

}

const path = route.record.path.toLowerCase();

const decodedPath = decode(path);

// also allow partial matching on the path

if (!filter.startsWith('/') &&

(decodedPath.includes(filter) || path.includes(filter)))

return true;

if (decodedPath.startsWith(filter) || path.startsWith(filter))

return true;

if (route.record.name && String(route.record.name).includes(filter))

return true;

return route.children.some(child => isRouteMatching(child, filter));

}

function omit(obj, keys) {

const ret = {};

for (const key in obj) {

if (!keys.includes(key)) {

// @ts-expect-error

ret[key] = obj[key];

}

}

return ret;

}

/**

* Creates a Router instance that can be used by a Vue app.

*

* @param options - {@link RouterOptions}

*/

function createRouter(options) {

const matcher = createRouterMatcher(options.routes, options);

const parseQuery$1 = options.parseQuery || parseQuery;

const stringifyQuery$1 = options.stringifyQuery || stringifyQuery;

const routerHistory = options.history;

if (!routerHistory)

throw new Error('Provide the "history" option when calling "createRouter()":' +

' https://next.router.vuejs.org/api/#history.');

const beforeGuards = useCallbacks();

const beforeResolveGuards = useCallbacks();

const afterGuards = useCallbacks();

const currentRoute = vue.shallowRef(START_LOCATION_NORMALIZED);

let pendingLocation = START_LOCATION_NORMALIZED;

// leave the scrollRestoration if no scrollBehavior is provided

if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {

history.scrollRestoration = 'manual';

}

const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue);

const encodeParams = applyToParams.bind(null, encodeParam);

const decodeParams =

// @ts-expect-error: intentionally avoid the type check

applyToParams.bind(null, decode);

function addRoute(parentOrRoute, route) {

let parent;

let record;

if (isRouteName(parentOrRoute)) {

parent = matcher.getRecordMatcher(parentOrRoute);

record = route;

}

else {

record = parentOrRoute;

}

return matcher.addRoute(record, parent);

}

function removeRoute(name) {

const recordMatcher = matcher.getRecordMatcher(name);

if (recordMatcher) {

matcher.removeRoute(recordMatcher);

}

else {

warn(`Cannot remove non-existent route "${String(name)}"`);

}

}

function getRoutes() {

return matcher.getRoutes().map(routeMatcher => routeMatcher.record);

}

function hasRoute(name) {

return !!matcher.getRecordMatcher(name);

}

function resolve(rawLocation, currentLocation) {

// const objectLocation = routerLocationAsObject(rawLocation)

// we create a copy to modify it later

currentLocation = assign({}, currentLocation || currentRoute.value);

if (typeof rawLocation === 'string') {

const locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);

const matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);

const href = routerHistory.createHref(locationNormalized.fullPath);

{

if (href.startsWith('//'))

warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);

else if (!matchedRoute.matched.length) {

warn(`No match found for location with path "${rawLocation}"`);

}

}

// locationNormalized is always a new object

return assign(locationNormalized, matchedRoute, {

params: decodeParams(matchedRoute.params),

hash: decode(locationNormalized.hash),

redirectedFrom: undefined,

href,

});

}

let matcherLocation;

// path could be relative in object as well

if ('path' in rawLocation) {

if ('params' in rawLocation &&

!('name' in rawLocation) &&

// @ts-expect-error: the type is never

Object.keys(rawLocation.params).length) {

warn(`Path "${

// @ts-expect-error: the type is never

rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);

}

matcherLocation = assign({}, rawLocation, {

path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,

});

}

else {

// remove any nullish param

const targetParams = assign({}, rawLocation.params);

for (const key in targetParams) {

if (targetParams[key] == null) {

delete targetParams[key];

}

}

// pass encoded values to the matcher so it can produce encoded path and fullPath

matcherLocation = assign({}, rawLocation, {

params: encodeParams(rawLocation.params),

});

// current location params are decoded, we need to encode them in case the

// matcher merges the params

currentLocation.params = encodeParams(currentLocation.params);

}

const matchedRoute = matcher.resolve(matcherLocation, currentLocation);

const hash = rawLocation.hash || '';

if (hash && !hash.startsWith('#')) {

warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);

}

// decoding them) the matcher might have merged current location params so

// we need to run the decoding again

matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));

const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {

hash: encodeHash(hash),

path: matchedRoute.path,

}));

const href = routerHistory.createHref(fullPath);

{

if (href.startsWith('//')) {

warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);

}

else if (!matchedRoute.matched.length) {

warn(`No match found for location with path "${'path' in rawLocation ? rawLocation.path : rawLocation}"`);

}

}

return assign({

fullPath,

// keep the hash encoded so fullPath is effectively path + encodedQuery +

// hash

hash,

query:

// if the user is using a custom query lib like qs, we might have

// nested objects, so we keep the query as is, meaning it can contain

// numbers at `$route.query`, but at the point, the user will have to

// use their own type anyway.

// https://github.com/vuejs/router/issues/328#issuecomment-649481567

stringifyQuery$1 === stringifyQuery

? normalizeQuery(rawLocation.query)

: (rawLocation.query || {}),

}, matchedRoute, {

redirectedFrom: undefined,

href,

});

}

function locationAsObject(to) {

return typeof to === 'string'

? parseURL(parseQuery$1, to, currentRoute.value.path)

: assign({}, to);

}

function checkCanceledNavigation(to, from) {

if (pendingLocation !== to) {

return createRouterError(8 /* NAVIGATION_CANCELLED */, {

from,

to,

});

}

}

function push(to) {

return pushWithRedirect(to);

}

function replace(to) {

return push(assign(locationAsObject(to), { replace: true }));

}

function handleRedirectRecord(to) {

const lastMatched = to.matched[to.matched.length - 1];

if (lastMatched && lastMatched.redirect) {

const { redirect } = lastMatched;

let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect;

if (typeof newTargetLocation === 'string') {

newTargetLocation =

newTargetLocation.includes('?') || newTargetLocation.includes('#')

? (newTargetLocation = locationAsObject(newTargetLocation))

: // force empty params

{ path: newTargetLocation };

// @ts-expect-error: force empty params when a string is passed to let

// the router parse them again

newTargetLocation.params = {};

}

if (!('path' in newTargetLocation) &&

!('name' in newTargetLocation)) {

warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${to.fullPath}". A redirect must contain a name or path. This will break in production.`);

throw new Error('Invalid redirect');

}

return assign({

query: to.query,

hash: to.hash,

params: to.params,

}, newTargetLocation);

}

}

function pushWithRedirect(to, redirectedFrom) {

const targetLocation = (pendingLocation = resolve(to));

const from = currentRoute.value;

const data = to.state;

const force = to.force;

// to could be a string where `replace` is a function

const replace = to.replace === true;

const shouldRedirect = handleRedirectRecord(targetLocation);

if (shouldRedirect)

return pushWithRedirect(assign(locationAsObject(shouldRedirect), {

state: data,

force,

replace,

}),

// keep original redirectedFrom if it exists

redirectedFrom || targetLocation);

// if it was a redirect we already called `pushWithRedirect` above

const toLocation = targetLocation;

toLocation.redirectedFrom = redirectedFrom;

let failure;

if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {

failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });

// trigger scroll to allow scrolling to the same anchor

handleScroll(from, from,

// this is a push, the only way for it to be triggered from a

// history.listen is with a redirect, which makes it become a push

true,

// This cannot be the first navigation because the initial location

// cannot be manually navigated to

false);

}

return (failure ? Promise.resolve(failure) : navigate(toLocation, from))

.catch((error) => isNavigationFailure(error)

? // navigation redirects still mark the router as ready

isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)

? error

: markAsReady(error) // also returns the error

: // reject any unknown error

triggerError(error, toLocation, from))

.then((failure) => {

if (failure) {

if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {

if (// we are redirecting to the same location we were already at

isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&

// and we have done it a couple of times

redirectedFrom &&

// @ts-expect-error: added only in dev

(redirectedFrom._count = redirectedFrom._count

? // @ts-expect-error

redirectedFrom._count + 1

: 1) > 10) {

warn(`Detected an infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow. This will break in production if not fixed.`);

return Promise.reject(new Error('Infinite redirect in navigation guard'));

}

return pushWithRedirect(

// keep options

assign(locationAsObject(failure.to), {

state: data,

force,

replace,

}),

// preserve the original redirectedFrom if any

redirectedFrom || toLocation);

}

}

else {

// if we fail we don't finalize the navigation

failure = finalizeNavigation(toLocation, from, true, replace, data);

}

triggerAfterEach(toLocation, from, failure);

return failure;

});

}

/**

* Helper to reject and skip all navigation guards if a new navigation happened

* @param to

* @param from

*/

function checkCanceledNavigationAndReject(to, from) {

const error = checkCanceledNavigation(to, from);

return error ? Promise.reject(error) : Promise.resolve();

}

// TODO: refactor the whole before guards by internally using router.beforeEach

function navigate(to, from) {

let guards;

const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);

// all components here have been resolved once because we are leaving

guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);

// leavingRecords is already reversed

for (const record of leavingRecords) {

record.leaveGuards.forEach(guard => {

guards.push(guardToPromiseFn(guard, to, from));

});

}

const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);

guards.push(canceledNavigationCheck);

// run the queue of per route beforeRouteLeave guards

return (runGuardQueue(guards)

.then(() => {

// check global guards beforeEach

guards = [];

for (const guard of beforeGuards.list()) {

guards.push(guardToPromiseFn(guard, to, from));

}

guards.push(canceledNavigationCheck);

return runGuardQueue(guards);

})

.then(() => {

// check in components beforeRouteUpdate

guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);

for (const record of updatingRecords) {

record.updateGuards.forEach(guard => {

guards.push(guardToPromiseFn(guard, to, from));

});

}

guards.push(canceledNavigationCheck);

// run the queue of per route beforeEnter guards

return runGuardQueue(guards);

})

.then(() => {

// check the route beforeEnter

guards = [];

for (const record of to.matched) {

// do not trigger beforeEnter on reused views

if (record.beforeEnter && !from.matched.includes(record)) {

if (Array.isArray(record.beforeEnter)) {

for (const beforeEnter of record.beforeEnter)

guards.push(guardToPromiseFn(beforeEnter, to, from));

}

else {

guards.push(guardToPromiseFn(record.beforeEnter, to, from));

}

}

}

guards.push(canceledNavigationCheck);

// run the queue of per route beforeEnter guards

return runGuardQueue(guards);

})

.then(() => {

// NOTE: at this point to.matched is normalized and does not contain any () => Promise

// clear existing enterCallbacks, these are added by extractComponentsGuards

to.matched.forEach(record => (record.enterCallbacks = {}));

// check in-component beforeRouteEnter

guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from);

guards.push(canceledNavigationCheck);

// run the queue of per route beforeEnter guards

return runGuardQueue(guards);

})

.then(() => {

// check global guards beforeResolve

guards = [];

for (const guard of beforeResolveGuards.list()) {

guards.push(guardToPromiseFn(guard, to, from));

}

guards.push(canceledNavigationCheck);

return runGuardQueue(guards);

})

// catch any navigation canceled

.catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)

? err

: Promise.reject(err)));

}

function triggerAfterEach(to, from, failure) {

// navigation is confirmed, call afterGuards

// TODO: wrap with error handlers

for (const guard of afterGuards.list())

guard(to, from, failure);

}

/**

* - Cleans up any navigation guards

* - Changes the url if necessary

* - Calls the scrollBehavior

*/

function finalizeNavigation(toLocation, from, isPush, replace, data) {

// a more recent navigation took place

const error = checkCanceledNavigation(toLocation, from);

if (error)

return error;

// only consider as push if it's not the first navigation

const isFirstNavigation = from === START_LOCATION_NORMALIZED;

const state = !isBrowser ? {} : history.state;

// change URL only if the user did a push/replace and if it's not the initial navigation because

// it's just reflecting the url

if (isPush) {

// on the initial navigation, we want to reuse the scroll position from

// history state if it exists

if (replace || isFirstNavigation)

routerHistory.replace(toLocation.fullPath, assign({

scroll: isFirstNavigation && state && state.scroll,

}, data));

else

routerHistory.push(toLocation.fullPath, data);

}

// accept current navigation

currentRoute.value = toLocation;

handleScroll(toLocation, from, isPush, isFirstNavigation);

markAsReady();

}

let removeHistoryListener;

// attach listener to history to trigger navigations

function setupListeners() {

removeHistoryListener = routerHistory.listen((to, _from, info) => {

// cannot be a redirect route because it was in history

const toLocation = resolve(to);

// due to dynamic routing, and to hash history with manual navigation

// (manually changing the url or calling history.hash = '#/somewhere'),

// there could be a redirect record in history

const shouldRedirect = handleRedirectRecord(toLocation);

if (shouldRedirect) {

pushWithRedirect(assign(shouldRedirect, { replace: true }), toLocation).catch(noop);

return;

}

pendingLocation = toLocation;

const from = currentRoute.value;

// TODO: should be moved to web history?

if (isBrowser) {

saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());

}

navigate(toLocation, from)

.catch((error) => {

if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {

return error;

}

if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {

// Here we could call if (info.delta) routerHistory.go(-info.delta,

// false) but this is bug prone as we have no way to wait the

// navigation to be finished before calling pushWithRedirect. Using

// a setTimeout of 16ms seems to work but there is not guarantee for

// it to work on every browser. So Instead we do not restore the

// history entry and trigger a new navigation as requested by the

// navigation guard.

// the error is already handled by router.push we just want to avoid

// logging the error

pushWithRedirect(error.to, toLocation

// avoid an uncaught rejection, let push call triggerError

)

.then(failure => {

// manual change in hash history #916 ending up in the URL not

// changing but it was changed by the manual url change, so we

// need to manually change it ourselves

if (isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ |

16 /* NAVIGATION_DUPLICATED */) &&

!info.delta &&

info.type === NavigationType.pop) {

routerHistory.go(-1, false);

}

})

.catch(noop);

// avoid the then branch

return Promise.reject();

}

// do not restore history on unknown direction

if (info.delta)

routerHistory.go(-info.delta, false);

// unrecognized error, transfer to the global handler

return triggerError(error, toLocation, from);

})

.then((failure) => {

failure =

failure ||

finalizeNavigation(

// after navigation, all matched components are resolved

toLocation, from, false);

// revert the navigation

if (failure) {

if (info.delta) {

routerHistory.go(-info.delta, false);

}

else if (info.type === NavigationType.pop &&

isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ | 16 /* NAVIGATION_DUPLICATED */)) {

// manual change in hash history #916

// it's like a push but lacks the information of the direction

routerHistory.go(-1, false);

}

}

triggerAfterEach(toLocation, from, failure);

})

.catch(noop);

});

}

// Initialization and Errors

let readyHandlers = useCallbacks();

let errorHandlers = useCallbacks();

let ready;

/**

* Trigger errorHandlers added via onError and throws the error as well

*

* @param error - error to throw

* @param to - location we were navigating to when the error happened

* @param from - location we were navigating from when the error happened

* @returns the error as a rejected promise

*/

function triggerError(error, to, from) {

markAsReady(error);

const list = errorHandlers.list();

if (list.length) {

list.forEach(handler => handler(error, to, from));

}

else {

{

warn('uncaught error during route navigation:');

}

console.error(error);

}

return Promise.reject(error);

}

function isReady() {

if (ready && currentRoute.value !== START_LOCATION_NORMALIZED)

return Promise.resolve();

return new Promise((resolve, reject) => {

readyHandlers.add([resolve, reject]);

});

}

function markAsReady(err) {

if (!ready) {

// still not ready if an error happened

ready = !err;

setupListeners();

readyHandlers

.list()

.forEach(([resolve, reject]) => (err ? reject(err) : resolve()));

readyHandlers.reset();

}

return err;

}

// Scroll behavior

function handleScroll(to, from, isPush, isFirstNavigation) {

const { scrollBehavior } = options;

if (!isBrowser || !scrollBehavior)

return Promise.resolve();

const scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||

((isFirstNavigation || !isPush) &&

history.state &&

history.state.scroll) ||

null;

return vue.nextTick()

.then(() => scrollBehavior(to, from, scrollPosition))

.then(position => position && scrollToPosition(position))

.catch(err => triggerError(err, to, from));

}

const go = (delta) => routerHistory.go(delta);

let started;

const installedApps = new Set();

const router = {

currentRoute,

addRoute,

removeRoute,

hasRoute,

getRoutes,

resolve,

options,

push,

replace,

go,

back: () => go(-1),

forward: () => go(1),

beforeEach: beforeGuards.add,

beforeResolve: beforeResolveGuards.add,

afterEach: afterGuards.add,

onError: errorHandlers.add,

isReady,

install(app) {

const router = this;

app.component('RouterLink', RouterLink);

app.component('RouterView', RouterView);

app.config.globalProperties.$router = router;

Object.defineProperty(app.config.globalProperties, '$route', {

enumerable: true,

get: () => vue.unref(currentRoute),

});

// this initial navigation is only necessary on client, on server it doesn't

// make sense because it will create an extra unnecessary navigation and could

// lead to problems

if (isBrowser &&

// used for the initial navigation client side to avoid pushing

// multiple times when the router is used in multiple apps

!started &&

currentRoute.value === START_LOCATION_NORMALIZED) {

// see above

started = true;

push(routerHistory.location).catch(err => {

warn('Unexpected error when starting the router:', err);

});

}

const reactiveRoute = {};

for (const key in START_LOCATION_NORMALIZED) {

// @ts-expect-error: the key matches

reactiveRoute[key] = vue.computed(() => currentRoute.value[key]);

}

app.provide(routerKey, router);

app.provide(routeLocationKey, vue.reactive(reactiveRoute));

app.provide(routerViewLocationKey, currentRoute);

const unmountApp = app.unmount;

installedApps.add(app);

app.unmount = function () {

installedApps.delete(app);

// the router is not attached to an app anymore

if (installedApps.size < 1) {

// invalidate the current navigation

pendingLocation = START_LOCATION_NORMALIZED;

removeHistoryListener && removeHistoryListener();

currentRoute.value = START_LOCATION_NORMALIZED;

started = false;

ready = false;

}

unmountApp();

};

if (isBrowser) {

addDevtools(app, router, matcher);

}

},

};

return router;

}

function runGuardQueue(guards) {

return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());

}

function extractChangingRecords(to, from) {

const leavingRecords = [];

const updatingRecords = [];

const enteringRecords = [];

const len = Math.max(from.matched.length, to.matched.length);

for (let i = 0; i < len; i++) {

const recordFrom = from.matched[i];

if (recordFrom) {

if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))

updatingRecords.push(recordFrom);

else

leavingRecords.push(recordFrom);

}

const recordTo = to.matched[i];

if (recordTo) {

// the type doesn't matter because we are comparing per reference

if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {

enteringRecords.push(recordTo);

}

}

}

return [leavingRecords, updatingRecords, enteringRecords];

}

/**

* Returns the router instance. Equivalent to using `$router` inside

* templates.

*/

function useRouter() {

return vue.inject(routerKey);

}

/**

* Returns the current route location. Equivalent to using `$route` inside

* templates.

*/

function useRoute() {

return vue.inject(routeLocationKey);

}

exports.RouterLink = RouterLink;

exports.RouterView = RouterView;

exports.START_LOCATION = START_LOCATION_NORMALIZED;

exports.createMemoryHistory = createMemoryHistory;

exports.createRouter = createRouter;

exports.createRouterMatcher = createRouterMatcher;

exports.createWebHashHistory = createWebHashHistory;

exports.createWebHistory = createWebHistory;

exports.isNavigationFailure = isNavigationFailure;

exports.matchedRouteKey = matchedRouteKey;

exports.onBeforeRouteLeave = onBeforeRouteLeave;

exports.onBeforeRouteUpdate = onBeforeRouteUpdate;

exports.parseQuery = parseQuery;

exports.routeLocationKey = routeLocationKey;

exports.routerKey = routerKey;

exports.routerViewLocationKey = routerViewLocationKey;

exports.stringifyQuery = stringifyQuery;

exports.useLink = useLink;

exports.useRoute = useRoute;

exports.useRouter = useRouter;

exports.viewDepthKey = viewDepthKey;

Object.defineProperty(exports, '__esModule', { value: true });

return exports;

})({}, Vue);

Vue路由基础

Vue属于单页应用(SPA),即整个应用程序中只有一个html页面。

在单页应用中(SPA),由于只是更改DOM来模拟多页面,所以页面浏览历史记录的功能就丧失了。此时,就需要

前端路由来实现浏览历史记录的功能。

home

news

注意:

上面代码中,router-link标签默认会被渲染成一个a标签

路由模式有两种:

createWebHistory 路由模式:路径中不带#号。(生产环境下不能直接访问,需要进行转发)

createWebHashHistory 路由模式:路径中带#号。

根路由与重定向

路由重定向:上面代码中,我们应该设置打开浏览器就默认调整到 “首页”,所以需要把根路由/重定向到/home。

修改路由配置:

//2、定义路由规则(每一个路径映射一个路由组件)

const routes = [

{

path: '/',

// component: Home //默认首页

redirect: '/home' //重定向

},{

path: '/home',

component: Home

},{

path: '/news',

component: News

}

];

嵌套路由

实际应用界面,通常由多层嵌套的组件组合而成。 比如,我们 “首页”组件中,还嵌套着 “登录”和 “注册”组件,那么

URL对应就是/home/login和/home/reg。

home

news

路由传参

路由传参有多种方式,这里我们学习两种:params与query。

params形式传参

home

news

注意:

使用v-bind绑定to属性。 to属性的值是一个json对象,此对象有两个属性:name属性和params属性。 name属性就是要路由的对象。所以,在路由规则列表中,每一个路由规则都应用有一个name值。 params属性就是要传递的参数。也是一个json对象。 组件接收参数时,使用 this.$route.params.参数名 的形式。

query形式传参 

home

news

注意:

to属性的值仍然是一个josn对象,但是两个属性变了,一个是path,一个是query。 path属性就是路由地址,对应路由规则中的path值。 query属性就是要传递的参数。也是一个json对象。 组件接收参数时,使用 this.$route.query.参数名 的形式。

params方式与query方式的区别

query方式传值:

 

params方式传值:

总结:params方式与query方式的区别:

query方式:类似于get方式,参数会在路由中显示,可以用做刷新后仍然存在的参数。利用路由规中 的path跳转。 params方式:类似于post方式,参数不会在路由中显示,页面刷新后参数将不存在。利用路由规则中

的name跳转。

restful风格传参

如果我们即想使用params方式传参,又不想在刷新时丢失参数,那么我们可以使用restful风格传参。

home

news

先在修改路由的path:'/news/:id/:name'

在router-link标签中,就可以使用restful风格传参了:to="/news/100/lisi"

编程式路由

利用JS实现路由跳转

router-link标签可以实现页面超链接形式的路由跳转。但是实际开发中,在很多情况下,需要通过某些逻辑判断来

确定如何进行路由跳转。也就是说:需要在js代码中进行路由跳转。此时可以使用编程式路由。

使用this.$router.push方法可以实现路由跳转,方法的第一个参数可为string类型的路径,或者可以通过对象

将相应参数传入。

通过this.$router.go(n)方法可以实现路由的前进后退,n表示跳转的个数,正数表示前进,负数表示后退。 如果只想实现前进后退可以使用this.(前进一页),以及 router.back()(后退一页)。


通过watch实现路由监听

通过watch属性设置监听$route变化,达到监听路由跳转的目的。

在上面代码中添加watch监听:

home

news

导航守卫

路由跳转前做一些验证,比如登录验证,是网站中的普遍需求。 对此,vue-route 提供了实现导航守卫

(navigation-guards)的功能。

你可以使用 router.beforeEach()函数 注册一个全局前置守卫,每个守卫方法接收三个参数:

to:即将要进入的目标路由对象(去哪里),可以使用 to.path 获取即将要进入路由地址。 from:当前导航正要离开的路由对象(从哪来),可以使用 from.path 获取正要离开的路由地址。 next:一个函数,表示继续执行下一个路由。(如果没有next,将不会进入到下一个路由)

下面例子中实现了如下功能:列举需要判断登录状态的 “路由集合”,当跳转至集合中的路由时:

如果是“未登录状态”,则一律跳转到登录页面

如果是“已登录状态”,则可以跳转到相应页面

login

home

news

相关文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。