UNPKG

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
/*! * 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