vue-router
Version:
> To see what versions are currently supported, please refer to the [Security Policy](./packages/router/SECURITY.md).
1,275 lines • 113 kB
JavaScript
/*!
* vue-router v5.2.0
* (c) 2026 Eduardo San Martin Morote
* @license MIT
*/
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let nostics = require("nostics");
let vue = require("vue");
let _vue_devtools_api = require("@vue/devtools-api");
//#region src/utils/env.ts
const isBrowser = typeof document !== "undefined";
//#endregion
//#region src/utils/index.ts
/**
* Allows differentiating lazy components from functional components and vue-class-component
* @internal
*
* @param component
*/
function isRouteComponent(component) {
return typeof component === "object" || "displayName" in component || "props" in component || "__vccOpts" in component;
}
function isESModule(obj) {
return obj.__esModule || obj[Symbol.toStringTag] === "Module" || obj.default && isRouteComponent(obj.default);
}
const assign = Object.assign;
function applyToParams(fn, params) {
const newParams = {};
for (const key in params) {
const value = params[key];
newParams[key] = isArray(value) ? value.map(fn) : fn(value);
}
return newParams;
}
const noop = () => {};
/**
* Typesafe alternative to Array.isArray
* https://github.com/microsoft/TypeScript/pull/48228
*
* @internal
*/
const isArray = Array.isArray;
function mergeOptions(defaults, partialOptions) {
const options = {};
for (const key in defaults) options[key] = key in partialOptions ? partialOptions[key] : defaults[key];
return options;
}
//#endregion
//#region src/errors.ts
const NavigationFailureSymbol = Symbol(process.env.NODE_ENV !== "production" ? "navigation failure" : "");
/**
* Enumeration with all possible types for navigation failures. Can be passed to
* {@link isNavigationFailure} to check for specific failures.
*/
let NavigationFailureType = /* @__PURE__ */ 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";
return NavigationFailureType;
}({});
const ErrorTypeMessages = {
[1]({ location, currentLocation }) {
return `No match for\n ${JSON.stringify(location)}${currentLocation ? "\nwhile being at\n" + JSON.stringify(currentLocation) : ""}`;
},
[2]({ from, to }) {
return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
},
[4]({ from, to }) {
return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
},
[8]({ from, to }) {
return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
},
[16]({ from, to: _to }) {
return `Avoided redundant navigation to current location: "${from.fullPath}".`;
}
};
/**
* Creates a typed NavigationFailure object.
* @internal
* @param type - NavigationFailureType
* @param params - { from, to }
*/
function createRouterError(type, params) {
if (process.env.NODE_ENV !== "production" || true) 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"
];
/**
* Stringifies a raw location for display in dev warnings.
*
* @internal
*/
function stringifyRoute(to) {
if (!to || typeof to === "string") return to;
if (to.path != null) return to.path;
const location = {};
for (const key of propertiesToLog) if (key in to) location[key] = to[key];
return JSON.stringify(location, null, 2);
}
//#endregion
//#region src/diagnostics.ts
/**
* Runtime diagnostics catalog for Vue Router.
*
* Every entry has a stable `VUE_ROUTER_R####` code, a `why` that states the problem
* (the diagnosis only, never the remedy) and a `fix` that states the remedy
* (only, never the diagnosis). They are complementary: the reporter prints
* both, so neither repeats the other. The diagnosis substrings asserted by the
* warning tests stay in `why`. All call sites stay behind the existing `__DEV__` (or
* `process.env.NODE_ENV !== 'production'`) guards and remain bare expression
* statements so they tree-shake out of production builds.
*
* Codes are permanent: never rename or reuse one.
* - `VUE_ROUTER_R0###` core runtime warnings
* - `VUE_ROUTER_R1###` experimental data-loaders
*/
const diagnostics = /*#__PURE__*/ (0, nostics.defineDiagnostics)({
reporters: [/*#__PURE__*/ (0, nostics.createConsoleReporter)()],
codes: {
VUE_ROUTER_R0001: {
why: (p) => `Parent route "${p.name}" not found when adding child route`,
fix: "Add the parent route before its children, or check the parent name for typos.",
docs: "https://router.vuejs.org/guide/advanced/dynamic-routing.html#Adding-nested-routes"
},
VUE_ROUTER_R0002: {
why: (p) => `Cannot remove non-existent route "${p.name}"`,
fix: "Check the route name; it may already have been removed or was never added.",
docs: "https://router.vuejs.org/guide/advanced/dynamic-routing.html#Removing-routes"
},
VUE_ROUTER_R0003: {
why: (p) => `Location "${stringifyRoute(p.location)}" resolved to "${p.href}". A resolved location cannot start with multiple slashes.`,
fix: "Remove the leading slashes from the location or fix the route configuration."
},
VUE_ROUTER_R0004: {
why: (p) => `No match found for location with path "${stringifyRoute(p.path)}"`,
fix: "Add a route matching this path or check for typos in the location.",
docs: "https://router.vuejs.org/guide/essentials/dynamic-matching.html#Catch-all-404-Not-found-Route"
},
VUE_ROUTER_R0005: {
why: (p) => `router.resolve() was passed an invalid location. This will fail in production.\nLocation: ${stringifyRoute(p.rawLocation)}`,
fix: "Pass a valid route location: a string path or an object with `path` or `name`."
},
VUE_ROUTER_R0006: {
why: (p) => `Path "${p.path}" was passed with params but they will be ignored because a "path" was passed.`,
fix: "Use a named route `{ name, params }` instead of `{ path, params }`.",
docs: "https://router.vuejs.org/guide/essentials/navigation.html#Navigate-to-a-different-location"
},
VUE_ROUTER_R0007: {
why: (p) => `A \`hash\` should always start with the character "#" but received "${p.hash}".`,
fix: (p) => `Prepend "#" to the hash in your route location: use "#${p.hash}".`
},
VUE_ROUTER_R0008: {
why: (p) => `Invalid redirect found:\n${p.target}\n when navigating to "${p.to}".\nThis will break in production.`,
fix: "A redirect must resolve to a location with a `name` or `path`; return one of those (or a string path) from `redirect`.",
docs: "https://router.vuejs.org/guide/essentials/redirect-and-alias.html#Redirect"
},
VUE_ROUTER_R0009: {
why: (p) => `Detected a possibly infinite redirection in a navigation guard when going from "${p.from}" to "${p.to}". Aborting to avoid a Stack Overflow. This might break in production if not fixed.`,
fix: "A guard is returning a new location on every call; make that return conditional so it only redirects when actually needed.",
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Global-Before-Guards"
},
VUE_ROUTER_R0010: {
why: "Uncaught error during route navigation",
fix: "Register an error handler with `router.onError()` to handle navigation errors."
},
VUE_ROUTER_R0011: {
why: "Unexpected error when starting the router:",
fix: "Inspect the actual cause; a navigation guard or async component likely threw during the initial navigation."
},
VUE_ROUTER_R0020: {
why: (p) => `No active route record was found when calling \`${p.fn}()\`. Maybe you called it inside of App.vue?`,
fix: "Call it from a component rendered inside <router-view> (a page component or one of its children), not from App.vue.",
docs: "https://router.vuejs.org/guide/advanced/composition-api.html#Navigation-Guards"
},
VUE_ROUTER_R0021: {
why: "No active route record was found when reactivating component with navigation guard. This is likely a bug in vue-router.",
fix: "Report with a minimal reproduction at https://github.com/vuejs/router/issues/new/choose."
},
VUE_ROUTER_R0022: {
why: (p) => `${p.fn}() was called outside of component setup but it must be called at the top of a setup function`,
fix: "Call it synchronously at the top of `setup()`, before any `await`.",
docs: "https://router.vuejs.org/guide/advanced/composition-api.html#Navigation-Guards"
},
VUE_ROUTER_R0023: {
why: (p) => `The "next" callback was never called inside of ${p.name ? `"${p.name}"` : ""}:\n${p.guard}`,
fix: "Make sure `next()` runs on every branch, including early returns and async paths, or drop the `next` parameter and return the value instead.",
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
},
VUE_ROUTER_R0024: {
why: (p) => `The "next" callback was called more than once in one navigation guard when going from "${p.from}" to "${p.to}". This will fail in production.`,
fix: "Call `next()` exactly once per guard: remove the extra call, or migrate to returning the value you passed to `next()`.",
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
},
VUE_ROUTER_R0025: {
why: "The `next()` callback in navigation guards is deprecated.",
fix: "Return the value instead: `next()` becomes `return`, `next(false)` becomes `return false`, `next(\"/path\")` becomes `return \"/path\"`.",
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
},
VUE_ROUTER_R0026: {
why: (p) => `Record with path "${p.path}" is either missing a "component(s)" or "children" property.`,
fix: "Add a `component`, `components`, or `children` to the route record.",
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html"
},
VUE_ROUTER_R0027: {
why: (p) => `Component "${p.name}" in record with path "${p.path}" is not a valid component. Received "${p.received}".`,
fix: "Pass a component or a function returning a Promise that resolves to one."
},
VUE_ROUTER_R0028: {
why: (p) => `Component "${p.name}" in record with path "${p.path}" is a Promise instead of a function that returns a Promise. This will break in production if not fixed.`,
fix: `Defer the import in an arrow function so it loads lazily: write "() => import('./MyPage.vue')", not "import('./MyPage.vue')".`,
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html"
},
VUE_ROUTER_R0029: {
why: (p) => `Component "${p.name}" in record with path "${p.path}" is defined using "defineAsyncComponent()".`,
fix: `Drop the wrapper and pass "() => import('./MyPage.vue')" directly; the router handles lazy components itself.`,
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html#Relationship-to-async-components"
},
VUE_ROUTER_R0030: {
why: (p) => `Component "${p.name}" in record with path "${p.path}" is a function that does not return a Promise. This will break in production if not fixed.`,
fix: "Return a dynamic import (`() => import(\"./MyPage.vue\")`) from the function, or add a `displayName` if it is a functional component.",
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html"
},
VUE_ROUTER_R0040: {
why: (p) => `Because "${p.el}" starts with "#", scrollBehavior resolves it as an element id via document.getElementById("${p.el.slice(1)}"), not as a CSS selector. No element has that id, but "${p.el}" does match an element with document.querySelector().`,
fix: (p) => `Resolve the element yourself and return the node: el: document.querySelector('${p.el}').`,
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
},
VUE_ROUTER_R0041: {
why: (p) => `The selector "${p.el}" is invalid. See https://mathiasbynens.be/notes/css-escapes or CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape) for the escaping rules.`,
fix: "Build an id selector as `#${CSS.escape(id)}` so special characters in the id are escaped.",
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
},
VUE_ROUTER_R0042: {
why: (p) => `Couldn't find element using selector "${p.el}" returned by scrollBehavior.`,
fix: "Return a selector that matches an existing element, or guard against missing elements.",
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
},
VUE_ROUTER_R0050: {
why: (p) => {
let to;
try {
to = p.to === void 0 ? "undefined" : JSON.stringify(p.to);
} catch {
to = String(p.to);
}
return `Invalid value for prop "to" in useLink()\n- to: ${to}`;
},
fix: "Pass a valid route location (a string path or an object) to the \"to\" prop."
},
VUE_ROUTER_R0060: {
why: (p) => `<router-view> can no longer be used directly inside <${p.comp}>.`,
fix: (p) => `Wrap the slot's resolved component with <${p.comp}> instead of nesting <router-view> in it:\n\n<router-view v-slot="{ Component }">\n <${p.comp}>\n <component :is="Component" />\n </${p.comp}>\n</router-view>`,
docs: "https://router.vuejs.org/guide/advanced/router-view-slot.html#KeepAlive-Transition"
},
VUE_ROUTER_R0070: {
why: (p) => `Cannot resolve a relative location without an absolute path. Trying to resolve "${p.to}" from "${p.from}".`,
fix: (p) => `Resolve from an absolute \`from\` path that starts with "/", e.g. "/${p.from}".`
},
VUE_ROUTER_R0080: {
why: (p) => `Error decoding "${p.text}". Using original value`,
fix: "Ensure the value is correctly percent-encoded."
},
VUE_ROUTER_R0090: {
why: (p) => `Found duplicated params with name "${p.name}" for path "${p.path}". Only the last one will be available on "$route.params".`,
fix: "Give each param a unique name within the path.",
docs: "https://router.vuejs.org/guide/essentials/route-matching-syntax.html"
},
VUE_ROUTER_R0100: {
why: (p) => `Discarded invalid param(s) "${p.params}" when navigating.` + p.inherited + ` See https://github.com/vuejs/router/commit/e887570 for more details.`,
fix: "Only pass params that exist on the target route."
},
VUE_ROUTER_R0101: {
why: (p) => `The Matcher cannot resolve relative paths but received "${p.path}". Unless you directly called \`matcher.resolve("${p.path}")\`, this is probably a bug in vue-router. Please open an issue at https://github.com/vuejs/router/issues/new/choose.`,
fix: "Pass an absolute path (starting with \"/\") to the matcher."
},
VUE_ROUTER_R0102: {
why: (p) => `Alias "${p.alias}" and the original record: "${p.original}" must have the exact same param named "${p.name}"`,
fix: "Use the same param names in the alias as in the original route.",
docs: "https://router.vuejs.org/guide/essentials/redirect-and-alias.html#Alias"
},
VUE_ROUTER_R0103: {
why: (p) => `The route named "${p.name}" has a child without a name, an empty path, and no children. Using that name won't render the empty path child, so this is probably a mistake.`,
fix: "Move the `name` onto the empty-path child; or, if intentional, give the child its own name to silence this.",
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html#Nested-Named-Routes"
},
VUE_ROUTER_R0104: {
why: (p) => `Absolute path "${p.path}" must have the exact same param named "${p.name}" as its parent "${p.parent}".`,
fix: "Include the parent route params in the absolute child path.",
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html"
},
VUE_ROUTER_R0105: {
why: (p) => `Finding ancestor route "${p.ancestor}" failed for "${p.record}"`,
fix: "Report a reproduction at https://github.com/vuejs/router/issues/new/choose."
},
VUE_ROUTER_R0110: {
why: `A hash base must end with a "#"`,
fix: (p) => `Append "#" to the "base" argument passed to "createWebHashHistory()": "${p.base}" should be "${p.suggestion}".`
},
VUE_ROUTER_R0120: {
why: "Error with push/replace State",
fix: "The browser rejected the history API call; check for cross-origin or rate-limit issues."
},
VUE_ROUTER_R0121: {
why: "history.state seems to have been manually replaced without preserving the necessary values.\nYou can find more information at https://router.vuejs.org/guide/migration/#Usage-of-history-state",
fix: "Merge the router's state into your own when calling it manually: `history.replaceState({ ...history.state, ...yourState }, '', url)`.",
docs: "https://router.vuejs.org/guide/migration.html#Usage-of-history-state"
},
VUE_ROUTER_R1001: {
why: (p) => `Data loader "${String(p.key)}" has a different parent than the current context. This shouldn't be happening.`,
fix: "Report a bug with a minimal reproduction at https://github.com/vuejs/router/."
},
VUE_ROUTER_R1002: {
why: "Returning a NavigationResult is deprecated.",
fix: "Replace `return new NavigationResult(to)` with `reroute(to)`, which throws internally to reroute.",
docs: "https://router.vuejs.org/data-loaders/navigation-aware.html#Controlling-the-navigation-with-reroute-"
},
VUE_ROUTER_R1003: {
why: (p) => `Loader "${p.key}"'s "commit()" was called but there is no staged data.`,
fix: "Ensure the loader resolved before calling `commit()`.",
docs: "https://router.vuejs.org/data-loaders/defining-loaders.html#Delaying-data-updates-with-commit"
},
VUE_ROUTER_R1004: {
why: (p) => "A loader returned a NavigationResult but is not registered on the route." + p.key,
fix: "Export the loader from the page component so it gets registered, e.g. `export const useUserData = defineLoader(...)`.",
docs: "https://router.vuejs.org/data-loaders/organization.html"
},
VUE_ROUTER_R1005: {
why: (p) => `Data loader "${p.key}" has itself as parent. This shouldn't be happening.`,
fix: "Report a bug with a minimal reproduction at https://github.com/vuejs/router/."
},
VUE_ROUTER_R1006: {
why: (p) => `A query was defined with the same key as the loader "[${p.key}]".\nSee https://pinia-colada.esm.dev/#TODO`,
fix: "If the key is meant to match, use the data loader directly; otherwise rename the `useQuery()` key so it no longer collides.",
docs: "https://router.vuejs.org/data-loaders/colada.html"
},
VUE_ROUTER_R1007: {
why: "Data Loader was setup twice.",
fix: "Register `DataLoaderPlugin` a single time via `app.use()`.",
docs: "https://router.vuejs.org/data-loaders.html#Installation"
},
VUE_ROUTER_R1008: {
why: "Data Loader is experimental and subject to breaking changes in the future.",
docs: "https://router.vuejs.org/data-loaders.html"
},
VUE_ROUTER_R1009: {
why: "Returning a NavigationResult from a loader is deprecated.",
fix: "Call `reroute(to)` inside the loader instead of returning `new NavigationResult(to)`; it throws internally to reroute.",
docs: "https://router.vuejs.org/data-loaders/navigation-aware.html#Controlling-the-navigation-with-reroute-"
}
}
});
//#endregion
//#region src/encoding.ts
/**
* 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 un-encoded 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 HASH_RE = /#/g;
const AMPERSAND_RE = /&/g;
const SLASH_RE = /\//g;
const EQUAL_RE = /=/g;
const IM_RE = /\?/g;
const PLUS_RE = /\+/g;
/**
* 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 text == null ? "" : 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).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 encodePath(text).replace(SLASH_RE, "%2F");
}
function decode(text) {
if (text == null) return null;
try {
return decodeURIComponent("" + text);
} catch {
process.env.NODE_ENV !== "production" && diagnostics.VUE_ROUTER_R0080({ text: "" + text });
}
return "" + text;
}
//#endregion
//#region src/location.ts
const TRAILING_SLASH_RE = /\/$/;
const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, "");
/**
* Transforms a 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 = "";
const hashPos = location.indexOf("#");
let searchPos = location.indexOf("?");
searchPos = hashPos >= 0 && searchPos > hashPos ? -1 : searchPos;
if (searchPos >= 0) {
path = location.slice(0, searchPos);
searchString = location.slice(searchPos, hashPos > 0 ? hashPos : location.length);
query = parseQuery(searchString.slice(1));
}
if (hashPos >= 0) {
path = path || location.slice(0, hashPos);
hash = location.slice(hashPos, location.length);
}
path = resolveRelativePath(path != null ? path : location, currentLocation);
return {
fullPath: path + searchString + hash,
path,
query,
hash: decode(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) {
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 stringifyQuery - A function that takes a query object of type LocationQueryRaw and returns a string representation of it.
* @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) {
return (a.aliasOf || a) === (b.aliasOf || b);
}
function isSameRouteLocationParams(a, b) {
if (Object.keys(a).length !== Object.keys(b).length) return false;
for (var key in a) if (!isSameRouteLocationParamsValue(a[key], b[key])) return false;
return true;
}
function isSameRouteLocationParamsValue(a, b) {
return isArray(a) ? isEquivalentArray(a, b) : isArray(b) ? isEquivalentArray(b, a) : (a && a.valueOf()) === (b && b.valueOf());
}
/**
* 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 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 (process.env.NODE_ENV !== "production" && !from.startsWith("/")) {
diagnostics.VUE_ROUTER_R0070({
to,
from
});
return to;
}
if (!to) return from;
const fromSegments = from.split("/");
const toSegments = to.split("/");
const lastToSegment = toSegments[toSegments.length - 1];
if (lastToSegment === ".." || lastToSegment === ".") toSegments.push("");
let position = fromSegments.length - 1;
let toPosition;
let segment;
for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
segment = toSegments[toPosition];
if (segment === ".") continue;
if (segment === "..") {
if (position > 1) position--;
} else break;
}
return fromSegments.slice(0, position).join("/") + "/" + toSegments.slice(toPosition).join("/");
}
/**
* 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: void 0,
params: {},
query: {},
hash: "",
fullPath: "/",
matched: [],
meta: {},
redirectedFrom: void 0
};
//#endregion
//#region src/history/common.ts
/**
* 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) {
const baseEl = document.querySelector("base");
base = baseEl && baseEl.getAttribute("href") || "/";
base = base.replace(/^\w+:\/\/[^/]+/, "");
} else base = "/";
if (base[0] !== "/" && base[0] !== "#") base = "/" + base;
return removeTrailingSlash(base);
}
const BEFORE_HASH_RE = /^[^#]+#/;
function createHref(base, location) {
return base.replace(BEFORE_HASH_RE, "#") + location;
}
//#endregion
//#region src/scrollBehavior.ts
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.scrollX,
top: window.scrollY
});
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 (process.env.NODE_ENV !== "production" && typeof position.el === "string") {
if (!isIdSelector || !document.getElementById(position.el.slice(1))) try {
const foundEl = document.querySelector(position.el);
if (isIdSelector && foundEl) {
diagnostics.VUE_ROUTER_R0040({ el: position.el });
return;
}
} catch {
diagnostics.VUE_ROUTER_R0041({ el: position.el });
return;
}
}
const el = typeof positionEl === "string" ? isIdSelector ? document.getElementById(positionEl.slice(1)) : document.querySelector(positionEl) : positionEl;
if (!el) {
process.env.NODE_ENV !== "production" && diagnostics.VUE_ROUTER_R0042({ el: position.el });
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.scrollX, scrollToOptions.top != null ? scrollToOptions.top : window.scrollY);
}
function getScrollKey(path, delta) {
return (history.state ? history.state.position - delta : -1) + path;
}
const scrollPositions = /* @__PURE__ */ new Map();
function saveScrollPosition(key, scrollPosition) {
scrollPositions.set(key, scrollPosition);
}
function getSavedScrollPosition(key) {
const scroll = scrollPositions.get(key);
scrollPositions.delete(key);
return scroll;
}
/**
* ScrollBehavior instance used by the router to compute and restore the scroll
* position when navigating.
*/
//#endregion
//#region src/history/html5.ts
let createBaseLocation = () => location.protocol + "//" + location.host;
/**
* Creates a normalized history location from a window.location object
* @param base - The base path
* @param location - The window.location object
*/
function createCurrentLocation(base, location) {
const { pathname, search, hash } = location;
const hashPos = base.indexOf("#");
if (hashPos > -1) {
let slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1;
let pathFromHash = hash.slice(slicePos);
if (pathFromHash[0] !== "/") pathFromHash = "/" + pathFromHash;
return stripBase(pathFromHash, "");
}
return stripBase(pathname, base) + search + hash;
}
function useHistoryListeners(base, historyState, currentLocation, replace) {
let listeners = [];
let teardowns = [];
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;
if (pauseState && pauseState === from) {
pauseState = null;
return;
}
delta = fromState ? state.position - fromState.position : 0;
} else replace(to);
listeners.forEach((listener) => {
listener(currentLocation.value, from, {
delta,
type: "pop",
direction: delta ? delta > 0 ? "forward" : "back" : ""
});
});
};
function pauseListeners() {
pauseState = currentLocation.value;
}
function listen(callback) {
listeners.push(callback);
const teardown = () => {
const index = listeners.indexOf(callback);
if (index > -1) listeners.splice(index, 1);
};
teardowns.push(teardown);
return teardown;
}
function beforeUnloadListener() {
if (document.visibilityState === "hidden") {
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("pagehide", beforeUnloadListener);
document.removeEventListener("visibilitychange", beforeUnloadListener);
}
window.addEventListener("popstate", popStateHandler);
window.addEventListener("pagehide", beforeUnloadListener);
document.addEventListener("visibilitychange", 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;
const currentLocation = { value: createCurrentLocation(base, location) };
const historyState = { value: history.state };
if (!historyState.value) changeLocation(currentLocation.value, {
back: null,
current: currentLocation.value,
forward: null,
position: history.length - 1,
replaced: true,
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 `<base>` 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 {
history[replace ? "replaceState" : "pushState"](state, "", url);
historyState.value = state;
} catch (err) {
if (process.env.NODE_ENV !== "production") diagnostics.VUE_ROUTER_R0120({ cause: err });
else console.error(err);
location[replace ? "replace" : "assign"](url);
}
}
function replace(to, data) {
changeLocation(to, assign({}, history.state, buildState(historyState.value.back, to, historyState.value.forward, true), data, { position: historyState.value.position }), true);
currentLocation.value = to;
}
function push(to, data) {
const currentState = assign({}, historyState.value, history.state, {
forward: to,
scroll: computeScrollPosition()
});
if (process.env.NODE_ENV !== "production" && !history.state) diagnostics.VUE_ROUTER_R0121();
changeLocation(currentState.current, currentState, true);
changeLocation(to, assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data), 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({
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;
}
//#endregion
//#region src/history/hash.ts
/**
* 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 `<base>` 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 `<base>` 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) {
base = location.host ? base || location.pathname + location.search : "";
if (!base.includes("#")) base += "#";
if (process.env.NODE_ENV !== "production" && !base.endsWith("#/") && !base.endsWith("#")) diagnostics.VUE_ROUTER_R0110({
base,
suggestion: base.replace(/#.*$/, "#")
});
return createWebHistory(base);
}
//#endregion
//#region src/history/memory.ts
/**
* Creates an 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 = [["", {}]];
let position = 0;
base = normalizeBase(base);
function setLocation(location, state = {}) {
position++;
if (position !== queue.length) queue.splice(position);
queue.push([location, state]);
}
function triggerListeners(to, from, { direction, delta }) {
const info = {
direction,
delta,
type: "pop"
};
for (const callback of listeners) callback(to, from, info);
}
const routerHistory = {
location: "",
state: {},
base,
createHref: createHref.bind(null, base),
replace(to, state) {
queue.splice(position--, 1);
setLocation(to, state);
},
push(to, state) {
setLocation(to, state);
},
listen(callback) {
listeners.push(callback);
return () => {
const index = listeners.indexOf(callback);
if (index > -1) listeners.splice(index, 1);
};
},
destroy() {
listeners = [];
queue = [["", {}]];
position = 0;
},
go(delta, shouldTrigger = true) {
const from = this.location;
const direction = delta < 0 ? "back" : "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][0]
});
Object.defineProperty(routerHistory, "state", {
enumerable: true,
get: () => queue[position][1]
});
return routerHistory;
}
//#endregion
//#region src/types/typeGuards.ts
function isRouteLocation(route) {
return typeof route === "string" || route && typeof route === "object";
}
function isRouteName(name) {
return typeof name === "string" || typeof name === "symbol";
}
//#endregion
//#region src/matcher/pathTokenizer.ts
const ROOT_TOKEN = {
type: 0,
value: ""
};
const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
function tokenizePath(path) {
if (!path) return [[]];
if (path === "/") return [[ROOT_TOKEN]];
if (!path.startsWith("/")) throw new Error(process.env.NODE_ENV !== "production" ? `Route paths should start with a "/": "${path}" should be "/${path}".` : `Invalid path "${path}"`);
function crash(message) {
throw new Error(`ERR (${state})/"${buffer}": ${message}`);
}
let state = 0;
let previousState = state;
const tokens = [];
let segment;
function finalizeSegment() {
if (segment) tokens.push(segment);
segment = [];
}
let i = 0;
let char;
let buffer = "";
let customRe = "";
function consumeBuffer() {
if (!buffer) return;
if (state === 0) segment.push({
type: 0,
value: buffer
});
else if (state === 1 || state === 2 || state === 3) {
if (segment.length > 1 && (char === "*" || char === "+")) crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
segment.push({
type: 1,
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++];
switch (state) {
case 0:
if (char === "\\") {
previousState = state;
state = 4;
} else if (char === "/") {
if (buffer) consumeBuffer();
finalizeSegment();
} else if (char === ":") {
consumeBuffer();
state = 1;
} else addCharToBuffer();
break;
case 4:
addCharToBuffer();
state = previousState;
break;
case 1:
if (char === "(") state = 2;
else if (VALID_PARAM_RE.test(char)) addCharToBuffer();
else {
consumeBuffer();
state = 0;
if (char !== "*" && char !== "?" && char !== "+") i--;
}
break;
case 2:
if (char === ")") if (customRe[customRe.length - 1] == "\\") customRe = customRe.slice(0, -1) + char;
else state = 3;
else customRe += char;
break;
case 3:
consumeBuffer();
state = 0;
if (char !== "*" && char !== "?" && char !== "+") i--;
customRe = "";
break;
default:
crash("Unknown state");
break;
}
}
if (state === 2) crash(`Unfinished custom RegExp for param "${buffer}"`);
consumeBuffer();
finalizeSegment();
return tokens;
}
//#endregion
//#region src/matcher/pathParserRanker.ts
const BASE_PARAM_PATTERN = "[^/]+?";
const BASE_PATH_PARSER_OPTIONS = {
sensitive: false,
strict: false,
start: true,
end: true
};
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);
const score = [];
let pattern = options.start ? "^" : "";
const keys = [];
for (const segment of segments) {
const segmentScores = segment.length ? [] : [90];
if (options.strict && !segment.length) pattern += "/";
for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
const token = segment[tokenIndex];
let subSegmentScore = 40 + (options.sensitive ? .25 : 0);
if (token.type === 0) {
if (!tokenIndex) pattern += "/";
pattern += token.value.replace(REGEX_CHARS_RE, "\\$&");
subSegmentScore += 40;
} else if (token.type === 1) {
const { value, repeatable, optional, regexp } = token;
keys.push({
name: value,
repeatable,
optional
});
const re = regexp ? regexp : BASE_PARAM_PATTERN;
if (re !== BASE_PARAM_PATTERN) {
subSegmentScore += 10;
try {
new RegExp(`(${re})`);
} catch (err) {
throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` + err.message);
}
}
let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
if (!tokenIndex) subPattern = optional && segment.length < 2 ? `(?:/${subPattern})` : "/" + subPattern;
if (optional) subPattern += "?";
pattern += subPattern;
subSegmentScore += 20;
if (optional) subSegmentScore += -8;
if (repeatable) subSegmentScore += -20;
if (re === ".*") subSegmentScore += -50;
}
segmentScores.push(subSegmentScore);
}
score.push(segmentScores);
}
if (options.strict && options.end) {
const i = score.length - 1;
score[i][score[i].length - 1] += .7000000000000001;
}
if (!options.strict) pattern += "/?";
if (options.end) pattern += "$";
else if (options.strict && !pattern.endsWith("/")) 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 = "";
let avoidDuplicatedSlash = false;
for (const segment of segments) {
if (!avoidDuplicatedSlash || !path.endsWith("/")) path += "/";
avoidDuplicatedSlash = false;
for (const token of segment) if (token.type === 0) path += token.value;
else if (token.type === 1) {
const { value, repeatable, optional } = token;
const param = value in params ? params[value] : "";
if (isArray(param) && !repeatable) throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
const text = isArray(param) ? param.join("/") : param;
if (!text) if (optional) {
if (segment.length < 2) if (path.endsWith("/")) path = path.slice(0, -1);
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];
if (diff) return diff;
i++;
}
if (a.length < b.length) return a.length === 1 && a[0] === 80 ? -1 : 1;
else if (a.length > b.length) return b.length === 1 && b[0] === 80 ? 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]);
if (comp) return comp;
i++;
}
if (Math.abs(bScor