vue-router.prod.cjs 112 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965
  1. /*!
  2. * vue-router v4.5.0
  3. * (c) 2024 Eduardo San Martin Morote
  4. * @license MIT
  5. */
  6. 'use strict';
  7. var vue = require('vue');
  8. const isBrowser = typeof document !== 'undefined';
  9. /**
  10. * Allows differentiating lazy components from functional components and vue-class-component
  11. * @internal
  12. *
  13. * @param component
  14. */
  15. function isRouteComponent(component) {
  16. return (typeof component === 'object' ||
  17. 'displayName' in component ||
  18. 'props' in component ||
  19. '__vccOpts' in component);
  20. }
  21. function isESModule(obj) {
  22. return (obj.__esModule ||
  23. obj[Symbol.toStringTag] === 'Module' ||
  24. // support CF with dynamic imports that do not
  25. // add the Module string tag
  26. (obj.default && isRouteComponent(obj.default)));
  27. }
  28. const assign = Object.assign;
  29. function applyToParams(fn, params) {
  30. const newParams = {};
  31. for (const key in params) {
  32. const value = params[key];
  33. newParams[key] = isArray(value)
  34. ? value.map(fn)
  35. : fn(value);
  36. }
  37. return newParams;
  38. }
  39. const noop = () => { };
  40. /**
  41. * Typesafe alternative to Array.isArray
  42. * https://github.com/microsoft/TypeScript/pull/48228
  43. */
  44. const isArray = Array.isArray;
  45. /**
  46. * Encoding Rules (␣ = Space)
  47. * - Path: ␣ " < > # ? { }
  48. * - Query: ␣ " < > # & =
  49. * - Hash: ␣ " < > `
  50. *
  51. * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
  52. * defines some extra characters to be encoded. Most browsers do not encode them
  53. * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
  54. * also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`)
  55. * plus `-._~`. This extra safety should be applied to query by patching the
  56. * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
  57. * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
  58. * into a `/` if directly typed in. The _backtick_ (`````) should also be
  59. * encoded everywhere because some browsers like FF encode it when directly
  60. * written while others don't. Safari and IE don't encode ``"<>{}``` in hash.
  61. */
  62. // const EXTRA_RESERVED_RE = /[!'()*]/g
  63. // const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)
  64. const HASH_RE = /#/g; // %23
  65. const AMPERSAND_RE = /&/g; // %26
  66. const SLASH_RE = /\//g; // %2F
  67. const EQUAL_RE = /=/g; // %3D
  68. const IM_RE = /\?/g; // %3F
  69. const PLUS_RE = /\+/g; // %2B
  70. /**
  71. * NOTE: It's not clear to me if we should encode the + symbol in queries, it
  72. * seems to be less flexible than not doing so and I can't find out the legacy
  73. * systems requiring this for regular requests like text/html. In the standard,
  74. * the encoding of the plus character is only mentioned for
  75. * application/x-www-form-urlencoded
  76. * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
  77. * leave the plus character as is in queries. To be more flexible, we allow the
  78. * plus character on the query, but it can also be manually encoded by the user.
  79. *
  80. * Resources:
  81. * - https://url.spec.whatwg.org/#urlencoded-parsing
  82. * - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
  83. */
  84. const ENC_BRACKET_OPEN_RE = /%5B/g; // [
  85. const ENC_BRACKET_CLOSE_RE = /%5D/g; // ]
  86. const ENC_CARET_RE = /%5E/g; // ^
  87. const ENC_BACKTICK_RE = /%60/g; // `
  88. const ENC_CURLY_OPEN_RE = /%7B/g; // {
  89. const ENC_PIPE_RE = /%7C/g; // |
  90. const ENC_CURLY_CLOSE_RE = /%7D/g; // }
  91. const ENC_SPACE_RE = /%20/g; // }
  92. /**
  93. * Encode characters that need to be encoded on the path, search and hash
  94. * sections of the URL.
  95. *
  96. * @internal
  97. * @param text - string to encode
  98. * @returns encoded string
  99. */
  100. function commonEncode(text) {
  101. return encodeURI('' + text)
  102. .replace(ENC_PIPE_RE, '|')
  103. .replace(ENC_BRACKET_OPEN_RE, '[')
  104. .replace(ENC_BRACKET_CLOSE_RE, ']');
  105. }
  106. /**
  107. * Encode characters that need to be encoded on the hash section of the URL.
  108. *
  109. * @param text - string to encode
  110. * @returns encoded string
  111. */
  112. function encodeHash(text) {
  113. return commonEncode(text)
  114. .replace(ENC_CURLY_OPEN_RE, '{')
  115. .replace(ENC_CURLY_CLOSE_RE, '}')
  116. .replace(ENC_CARET_RE, '^');
  117. }
  118. /**
  119. * Encode characters that need to be encoded query values on the query
  120. * section of the URL.
  121. *
  122. * @param text - string to encode
  123. * @returns encoded string
  124. */
  125. function encodeQueryValue(text) {
  126. return (commonEncode(text)
  127. // Encode the space as +, encode the + to differentiate it from the space
  128. .replace(PLUS_RE, '%2B')
  129. .replace(ENC_SPACE_RE, '+')
  130. .replace(HASH_RE, '%23')
  131. .replace(AMPERSAND_RE, '%26')
  132. .replace(ENC_BACKTICK_RE, '`')
  133. .replace(ENC_CURLY_OPEN_RE, '{')
  134. .replace(ENC_CURLY_CLOSE_RE, '}')
  135. .replace(ENC_CARET_RE, '^'));
  136. }
  137. /**
  138. * Like `encodeQueryValue` but also encodes the `=` character.
  139. *
  140. * @param text - string to encode
  141. */
  142. function encodeQueryKey(text) {
  143. return encodeQueryValue(text).replace(EQUAL_RE, '%3D');
  144. }
  145. /**
  146. * Encode characters that need to be encoded on the path section of the URL.
  147. *
  148. * @param text - string to encode
  149. * @returns encoded string
  150. */
  151. function encodePath(text) {
  152. return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F');
  153. }
  154. /**
  155. * Encode characters that need to be encoded on the path section of the URL as a
  156. * param. This function encodes everything {@link encodePath} does plus the
  157. * slash (`/`) character. If `text` is `null` or `undefined`, returns an empty
  158. * string instead.
  159. *
  160. * @param text - string to encode
  161. * @returns encoded string
  162. */
  163. function encodeParam(text) {
  164. return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F');
  165. }
  166. /**
  167. * Decode text using `decodeURIComponent`. Returns the original text if it
  168. * fails.
  169. *
  170. * @param text - string to decode
  171. * @returns decoded string
  172. */
  173. function decode(text) {
  174. try {
  175. return decodeURIComponent('' + text);
  176. }
  177. catch (err) {
  178. }
  179. return '' + text;
  180. }
  181. const TRAILING_SLASH_RE = /\/$/;
  182. const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
  183. /**
  184. * Transforms a URI into a normalized history location
  185. *
  186. * @param parseQuery
  187. * @param location - URI to normalize
  188. * @param currentLocation - current absolute location. Allows resolving relative
  189. * paths. Must start with `/`. Defaults to `/`
  190. * @returns a normalized history location
  191. */
  192. function parseURL(parseQuery, location, currentLocation = '/') {
  193. let path, query = {}, searchString = '', hash = '';
  194. // Could use URL and URLSearchParams but IE 11 doesn't support it
  195. // TODO: move to new URL()
  196. const hashPos = location.indexOf('#');
  197. let searchPos = location.indexOf('?');
  198. // the hash appears before the search, so it's not part of the search string
  199. if (hashPos < searchPos && hashPos >= 0) {
  200. searchPos = -1;
  201. }
  202. if (searchPos > -1) {
  203. path = location.slice(0, searchPos);
  204. searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
  205. query = parseQuery(searchString);
  206. }
  207. if (hashPos > -1) {
  208. path = path || location.slice(0, hashPos);
  209. // keep the # character
  210. hash = location.slice(hashPos, location.length);
  211. }
  212. // no search and no query
  213. path = resolveRelativePath(path != null ? path : location, currentLocation);
  214. // empty path means a relative query or hash `?foo=f`, `#thing`
  215. return {
  216. fullPath: path + (searchString && '?') + searchString + hash,
  217. path,
  218. query,
  219. hash: decode(hash),
  220. };
  221. }
  222. /**
  223. * Stringifies a URL object
  224. *
  225. * @param stringifyQuery
  226. * @param location
  227. */
  228. function stringifyURL(stringifyQuery, location) {
  229. const query = location.query ? stringifyQuery(location.query) : '';
  230. return location.path + (query && '?') + query + (location.hash || '');
  231. }
  232. /**
  233. * Strips off the base from the beginning of a location.pathname in a non-case-sensitive way.
  234. *
  235. * @param pathname - location.pathname
  236. * @param base - base to strip off
  237. */
  238. function stripBase(pathname, base) {
  239. // no base or base is not found at the beginning
  240. if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase()))
  241. return pathname;
  242. return pathname.slice(base.length) || '/';
  243. }
  244. /**
  245. * Checks if two RouteLocation are equal. This means that both locations are
  246. * pointing towards the same {@link RouteRecord} and that all `params`, `query`
  247. * parameters and `hash` are the same
  248. *
  249. * @param stringifyQuery - A function that takes a query object of type LocationQueryRaw and returns a string representation of it.
  250. * @param a - first {@link RouteLocation}
  251. * @param b - second {@link RouteLocation}
  252. */
  253. function isSameRouteLocation(stringifyQuery, a, b) {
  254. const aLastIndex = a.matched.length - 1;
  255. const bLastIndex = b.matched.length - 1;
  256. return (aLastIndex > -1 &&
  257. aLastIndex === bLastIndex &&
  258. isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) &&
  259. isSameRouteLocationParams(a.params, b.params) &&
  260. stringifyQuery(a.query) === stringifyQuery(b.query) &&
  261. a.hash === b.hash);
  262. }
  263. /**
  264. * Check if two `RouteRecords` are equal. Takes into account aliases: they are
  265. * considered equal to the `RouteRecord` they are aliasing.
  266. *
  267. * @param a - first {@link RouteRecord}
  268. * @param b - second {@link RouteRecord}
  269. */
  270. function isSameRouteRecord(a, b) {
  271. // since the original record has an undefined value for aliasOf
  272. // but all aliases point to the original record, this will always compare
  273. // the original record
  274. return (a.aliasOf || a) === (b.aliasOf || b);
  275. }
  276. function isSameRouteLocationParams(a, b) {
  277. if (Object.keys(a).length !== Object.keys(b).length)
  278. return false;
  279. for (const key in a) {
  280. if (!isSameRouteLocationParamsValue(a[key], b[key]))
  281. return false;
  282. }
  283. return true;
  284. }
  285. function isSameRouteLocationParamsValue(a, b) {
  286. return isArray(a)
  287. ? isEquivalentArray(a, b)
  288. : isArray(b)
  289. ? isEquivalentArray(b, a)
  290. : a === b;
  291. }
  292. /**
  293. * Check if two arrays are the same or if an array with one single entry is the
  294. * same as another primitive value. Used to check query and parameters
  295. *
  296. * @param a - array of values
  297. * @param b - array of values or a single value
  298. */
  299. function isEquivalentArray(a, b) {
  300. return isArray(b)
  301. ? a.length === b.length && a.every((value, i) => value === b[i])
  302. : a.length === 1 && a[0] === b;
  303. }
  304. /**
  305. * Resolves a relative path that starts with `.`.
  306. *
  307. * @param to - path location we are resolving
  308. * @param from - currentLocation.path, should start with `/`
  309. */
  310. function resolveRelativePath(to, from) {
  311. if (to.startsWith('/'))
  312. return to;
  313. if (!to)
  314. return from;
  315. const fromSegments = from.split('/');
  316. const toSegments = to.split('/');
  317. const lastToSegment = toSegments[toSegments.length - 1];
  318. // make . and ./ the same (../ === .., ../../ === ../..)
  319. // this is the same behavior as new URL()
  320. if (lastToSegment === '..' || lastToSegment === '.') {
  321. toSegments.push('');
  322. }
  323. let position = fromSegments.length - 1;
  324. let toPosition;
  325. let segment;
  326. for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
  327. segment = toSegments[toPosition];
  328. // we stay on the same position
  329. if (segment === '.')
  330. continue;
  331. // go up in the from array
  332. if (segment === '..') {
  333. // we can't go below zero, but we still need to increment toPosition
  334. if (position > 1)
  335. position--;
  336. // continue
  337. }
  338. // we reached a non-relative path, we stop here
  339. else
  340. break;
  341. }
  342. return (fromSegments.slice(0, position).join('/') +
  343. '/' +
  344. toSegments.slice(toPosition).join('/'));
  345. }
  346. /**
  347. * Initial route location where the router is. Can be used in navigation guards
  348. * to differentiate the initial navigation.
  349. *
  350. * @example
  351. * ```js
  352. * import { START_LOCATION } from 'vue-router'
  353. *
  354. * router.beforeEach((to, from) => {
  355. * if (from === START_LOCATION) {
  356. * // initial navigation
  357. * }
  358. * })
  359. * ```
  360. */
  361. const START_LOCATION_NORMALIZED = {
  362. path: '/',
  363. // TODO: could we use a symbol in the future?
  364. name: undefined,
  365. params: {},
  366. query: {},
  367. hash: '',
  368. fullPath: '/',
  369. matched: [],
  370. meta: {},
  371. redirectedFrom: undefined,
  372. };
  373. var NavigationType;
  374. (function (NavigationType) {
  375. NavigationType["pop"] = "pop";
  376. NavigationType["push"] = "push";
  377. })(NavigationType || (NavigationType = {}));
  378. var NavigationDirection;
  379. (function (NavigationDirection) {
  380. NavigationDirection["back"] = "back";
  381. NavigationDirection["forward"] = "forward";
  382. NavigationDirection["unknown"] = "";
  383. })(NavigationDirection || (NavigationDirection = {}));
  384. /**
  385. * Starting location for Histories
  386. */
  387. const START = '';
  388. // Generic utils
  389. /**
  390. * Normalizes a base by removing any trailing slash and reading the base tag if
  391. * present.
  392. *
  393. * @param base - base to normalize
  394. */
  395. function normalizeBase(base) {
  396. if (!base) {
  397. if (isBrowser) {
  398. // respect <base> tag
  399. const baseEl = document.querySelector('base');
  400. base = (baseEl && baseEl.getAttribute('href')) || '/';
  401. // strip full URL origin
  402. base = base.replace(/^\w+:\/\/[^\/]+/, '');
  403. }
  404. else {
  405. base = '/';
  406. }
  407. }
  408. // ensure leading slash when it was removed by the regex above avoid leading
  409. // slash with hash because the file could be read from the disk like file://
  410. // and the leading slash would cause problems
  411. if (base[0] !== '/' && base[0] !== '#')
  412. base = '/' + base;
  413. // remove the trailing slash so all other method can just do `base + fullPath`
  414. // to build an href
  415. return removeTrailingSlash(base);
  416. }
  417. // remove any character before the hash
  418. const BEFORE_HASH_RE = /^[^#]+#/;
  419. function createHref(base, location) {
  420. return base.replace(BEFORE_HASH_RE, '#') + location;
  421. }
  422. function getElementPosition(el, offset) {
  423. const docRect = document.documentElement.getBoundingClientRect();
  424. const elRect = el.getBoundingClientRect();
  425. return {
  426. behavior: offset.behavior,
  427. left: elRect.left - docRect.left - (offset.left || 0),
  428. top: elRect.top - docRect.top - (offset.top || 0),
  429. };
  430. }
  431. const computeScrollPosition = () => ({
  432. left: window.scrollX,
  433. top: window.scrollY,
  434. });
  435. function scrollToPosition(position) {
  436. let scrollToOptions;
  437. if ('el' in position) {
  438. const positionEl = position.el;
  439. const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#');
  440. const el = typeof positionEl === 'string'
  441. ? isIdSelector
  442. ? document.getElementById(positionEl.slice(1))
  443. : document.querySelector(positionEl)
  444. : positionEl;
  445. if (!el) {
  446. return;
  447. }
  448. scrollToOptions = getElementPosition(el, position);
  449. }
  450. else {
  451. scrollToOptions = position;
  452. }
  453. if ('scrollBehavior' in document.documentElement.style)
  454. window.scrollTo(scrollToOptions);
  455. else {
  456. window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.scrollX, scrollToOptions.top != null ? scrollToOptions.top : window.scrollY);
  457. }
  458. }
  459. function getScrollKey(path, delta) {
  460. const position = history.state ? history.state.position - delta : -1;
  461. return position + path;
  462. }
  463. const scrollPositions = new Map();
  464. function saveScrollPosition(key, scrollPosition) {
  465. scrollPositions.set(key, scrollPosition);
  466. }
  467. function getSavedScrollPosition(key) {
  468. const scroll = scrollPositions.get(key);
  469. // consume it so it's not used again
  470. scrollPositions.delete(key);
  471. return scroll;
  472. }
  473. // TODO: RFC about how to save scroll position
  474. /**
  475. * ScrollBehavior instance used by the router to compute and restore the scroll
  476. * position when navigating.
  477. */
  478. // export interface ScrollHandler<ScrollPositionEntry extends HistoryStateValue, ScrollPosition extends ScrollPositionEntry> {
  479. // // returns a scroll position that can be saved in history
  480. // compute(): ScrollPositionEntry
  481. // // can take an extended ScrollPositionEntry
  482. // scroll(position: ScrollPosition): void
  483. // }
  484. // export const scrollHandler: ScrollHandler<ScrollPosition> = {
  485. // compute: computeScroll,
  486. // scroll: scrollToPosition,
  487. // }
  488. let createBaseLocation = () => location.protocol + '//' + location.host;
  489. /**
  490. * Creates a normalized history location from a window.location object
  491. * @param base - The base path
  492. * @param location - The window.location object
  493. */
  494. function createCurrentLocation(base, location) {
  495. const { pathname, search, hash } = location;
  496. // allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
  497. const hashPos = base.indexOf('#');
  498. if (hashPos > -1) {
  499. let slicePos = hash.includes(base.slice(hashPos))
  500. ? base.slice(hashPos).length
  501. : 1;
  502. let pathFromHash = hash.slice(slicePos);
  503. // prepend the starting slash to hash so the url starts with /#
  504. if (pathFromHash[0] !== '/')
  505. pathFromHash = '/' + pathFromHash;
  506. return stripBase(pathFromHash, '');
  507. }
  508. const path = stripBase(pathname, base);
  509. return path + search + hash;
  510. }
  511. function useHistoryListeners(base, historyState, currentLocation, replace) {
  512. let listeners = [];
  513. let teardowns = [];
  514. // TODO: should it be a stack? a Dict. Check if the popstate listener
  515. // can trigger twice
  516. let pauseState = null;
  517. const popStateHandler = ({ state, }) => {
  518. const to = createCurrentLocation(base, location);
  519. const from = currentLocation.value;
  520. const fromState = historyState.value;
  521. let delta = 0;
  522. if (state) {
  523. currentLocation.value = to;
  524. historyState.value = state;
  525. // ignore the popstate and reset the pauseState
  526. if (pauseState && pauseState === from) {
  527. pauseState = null;
  528. return;
  529. }
  530. delta = fromState ? state.position - fromState.position : 0;
  531. }
  532. else {
  533. replace(to);
  534. }
  535. // Here we could also revert the navigation by calling history.go(-delta)
  536. // this listener will have to be adapted to not trigger again and to wait for the url
  537. // to be updated before triggering the listeners. Some kind of validation function would also
  538. // need to be passed to the listeners so the navigation can be accepted
  539. // call all listeners
  540. listeners.forEach(listener => {
  541. listener(currentLocation.value, from, {
  542. delta,
  543. type: NavigationType.pop,
  544. direction: delta
  545. ? delta > 0
  546. ? NavigationDirection.forward
  547. : NavigationDirection.back
  548. : NavigationDirection.unknown,
  549. });
  550. });
  551. };
  552. function pauseListeners() {
  553. pauseState = currentLocation.value;
  554. }
  555. function listen(callback) {
  556. // set up the listener and prepare teardown callbacks
  557. listeners.push(callback);
  558. const teardown = () => {
  559. const index = listeners.indexOf(callback);
  560. if (index > -1)
  561. listeners.splice(index, 1);
  562. };
  563. teardowns.push(teardown);
  564. return teardown;
  565. }
  566. function beforeUnloadListener() {
  567. const { history } = window;
  568. if (!history.state)
  569. return;
  570. history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');
  571. }
  572. function destroy() {
  573. for (const teardown of teardowns)
  574. teardown();
  575. teardowns = [];
  576. window.removeEventListener('popstate', popStateHandler);
  577. window.removeEventListener('beforeunload', beforeUnloadListener);
  578. }
  579. // set up the listeners and prepare teardown callbacks
  580. window.addEventListener('popstate', popStateHandler);
  581. // TODO: could we use 'pagehide' or 'visibilitychange' instead?
  582. // https://developer.chrome.com/blog/page-lifecycle-api/
  583. window.addEventListener('beforeunload', beforeUnloadListener, {
  584. passive: true,
  585. });
  586. return {
  587. pauseListeners,
  588. listen,
  589. destroy,
  590. };
  591. }
  592. /**
  593. * Creates a state object
  594. */
  595. function buildState(back, current, forward, replaced = false, computeScroll = false) {
  596. return {
  597. back,
  598. current,
  599. forward,
  600. replaced,
  601. position: window.history.length,
  602. scroll: computeScroll ? computeScrollPosition() : null,
  603. };
  604. }
  605. function useHistoryStateNavigation(base) {
  606. const { history, location } = window;
  607. // private variables
  608. const currentLocation = {
  609. value: createCurrentLocation(base, location),
  610. };
  611. const historyState = { value: history.state };
  612. // build current history entry as this is a fresh navigation
  613. if (!historyState.value) {
  614. changeLocation(currentLocation.value, {
  615. back: null,
  616. current: currentLocation.value,
  617. forward: null,
  618. // the length is off by one, we need to decrease it
  619. position: history.length - 1,
  620. replaced: true,
  621. // don't add a scroll as the user may have an anchor, and we want
  622. // scrollBehavior to be triggered without a saved position
  623. scroll: null,
  624. }, true);
  625. }
  626. function changeLocation(to, state, replace) {
  627. /**
  628. * if a base tag is provided, and we are on a normal domain, we have to
  629. * respect the provided `base` attribute because pushState() will use it and
  630. * potentially erase anything before the `#` like at
  631. * https://github.com/vuejs/router/issues/685 where a base of
  632. * `/folder/#` but a base of `/` would erase the `/folder/` section. If
  633. * there is no host, the `<base>` tag makes no sense and if there isn't a
  634. * base tag we can just use everything after the `#`.
  635. */
  636. const hashIndex = base.indexOf('#');
  637. const url = hashIndex > -1
  638. ? (location.host && document.querySelector('base')
  639. ? base
  640. : base.slice(hashIndex)) + to
  641. : createBaseLocation() + base + to;
  642. try {
  643. // BROWSER QUIRK
  644. // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
  645. history[replace ? 'replaceState' : 'pushState'](state, '', url);
  646. historyState.value = state;
  647. }
  648. catch (err) {
  649. {
  650. console.error(err);
  651. }
  652. // Force the navigation, this also resets the call count
  653. location[replace ? 'replace' : 'assign'](url);
  654. }
  655. }
  656. function replace(to, data) {
  657. const state = assign({}, history.state, buildState(historyState.value.back,
  658. // keep back and forward entries but override current position
  659. to, historyState.value.forward, true), data, { position: historyState.value.position });
  660. changeLocation(to, state, true);
  661. currentLocation.value = to;
  662. }
  663. function push(to, data) {
  664. // Add to current entry the information of where we are going
  665. // as well as saving the current position
  666. const currentState = assign({},
  667. // use current history state to gracefully handle a wrong call to
  668. // history.replaceState
  669. // https://github.com/vuejs/router/issues/366
  670. historyState.value, history.state, {
  671. forward: to,
  672. scroll: computeScrollPosition(),
  673. });
  674. changeLocation(currentState.current, currentState, true);
  675. const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
  676. changeLocation(to, state, false);
  677. currentLocation.value = to;
  678. }
  679. return {
  680. location: currentLocation,
  681. state: historyState,
  682. push,
  683. replace,
  684. };
  685. }
  686. /**
  687. * Creates an HTML5 history. Most common history for single page applications.
  688. *
  689. * @param base -
  690. */
  691. function createWebHistory(base) {
  692. base = normalizeBase(base);
  693. const historyNavigation = useHistoryStateNavigation(base);
  694. const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);
  695. function go(delta, triggerListeners = true) {
  696. if (!triggerListeners)
  697. historyListeners.pauseListeners();
  698. history.go(delta);
  699. }
  700. const routerHistory = assign({
  701. // it's overridden right after
  702. location: '',
  703. base,
  704. go,
  705. createHref: createHref.bind(null, base),
  706. }, historyNavigation, historyListeners);
  707. Object.defineProperty(routerHistory, 'location', {
  708. enumerable: true,
  709. get: () => historyNavigation.location.value,
  710. });
  711. Object.defineProperty(routerHistory, 'state', {
  712. enumerable: true,
  713. get: () => historyNavigation.state.value,
  714. });
  715. return routerHistory;
  716. }
  717. /**
  718. * 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.
  719. * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
  720. *
  721. * @param base - Base applied to all urls, defaults to '/'
  722. * @returns a history object that can be passed to the router constructor
  723. */
  724. function createMemoryHistory(base = '') {
  725. let listeners = [];
  726. let queue = [START];
  727. let position = 0;
  728. base = normalizeBase(base);
  729. function setLocation(location) {
  730. position++;
  731. if (position !== queue.length) {
  732. // we are in the middle, we remove everything from here in the queue
  733. queue.splice(position);
  734. }
  735. queue.push(location);
  736. }
  737. function triggerListeners(to, from, { direction, delta }) {
  738. const info = {
  739. direction,
  740. delta,
  741. type: NavigationType.pop,
  742. };
  743. for (const callback of listeners) {
  744. callback(to, from, info);
  745. }
  746. }
  747. const routerHistory = {
  748. // rewritten by Object.defineProperty
  749. location: START,
  750. // TODO: should be kept in queue
  751. state: {},
  752. base,
  753. createHref: createHref.bind(null, base),
  754. replace(to) {
  755. // remove current entry and decrement position
  756. queue.splice(position--, 1);
  757. setLocation(to);
  758. },
  759. push(to, data) {
  760. setLocation(to);
  761. },
  762. listen(callback) {
  763. listeners.push(callback);
  764. return () => {
  765. const index = listeners.indexOf(callback);
  766. if (index > -1)
  767. listeners.splice(index, 1);
  768. };
  769. },
  770. destroy() {
  771. listeners = [];
  772. queue = [START];
  773. position = 0;
  774. },
  775. go(delta, shouldTrigger = true) {
  776. const from = this.location;
  777. const direction =
  778. // we are considering delta === 0 going forward, but in abstract mode
  779. // using 0 for the delta doesn't make sense like it does in html5 where
  780. // it reloads the page
  781. delta < 0 ? NavigationDirection.back : NavigationDirection.forward;
  782. position = Math.max(0, Math.min(position + delta, queue.length - 1));
  783. if (shouldTrigger) {
  784. triggerListeners(this.location, from, {
  785. direction,
  786. delta,
  787. });
  788. }
  789. },
  790. };
  791. Object.defineProperty(routerHistory, 'location', {
  792. enumerable: true,
  793. get: () => queue[position],
  794. });
  795. return routerHistory;
  796. }
  797. /**
  798. * Creates a hash history. Useful for web applications with no host (e.g. `file://`) or when configuring a server to
  799. * handle any URL is not possible.
  800. *
  801. * @param base - optional base to provide. Defaults to `location.pathname + location.search` If there is a `<base>` tag
  802. * in the `head`, its value will be ignored in favor of this parameter **but note it affects all the history.pushState()
  803. * calls**, meaning that if you use a `<base>` tag, it's `href` value **has to match this parameter** (ignoring anything
  804. * after the `#`).
  805. *
  806. * @example
  807. * ```js
  808. * // at https://example.com/folder
  809. * createWebHashHistory() // gives a url of `https://example.com/folder#`
  810. * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#`
  811. * // if the `#` is provided in the base, it won't be added by `createWebHashHistory`
  812. * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/`
  813. * // you should avoid doing this because it changes the original url and breaks copying urls
  814. * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#`
  815. *
  816. * // at file:///usr/etc/folder/index.html
  817. * // for locations with no `host`, the base is ignored
  818. * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#`
  819. * ```
  820. */
  821. function createWebHashHistory(base) {
  822. // Make sure this implementation is fine in terms of encoding, specially for IE11
  823. // for `file://`, directly use the pathname and ignore the base
  824. // location.pathname contains an initial `/` even at the root: `https://example.com`
  825. base = location.host ? base || location.pathname + location.search : '';
  826. // allow the user to provide a `#` in the middle: `/base/#/app`
  827. if (!base.includes('#'))
  828. base += '#';
  829. return createWebHistory(base);
  830. }
  831. function isRouteLocation(route) {
  832. return typeof route === 'string' || (route && typeof route === 'object');
  833. }
  834. function isRouteName(name) {
  835. return typeof name === 'string' || typeof name === 'symbol';
  836. }
  837. const NavigationFailureSymbol = Symbol('');
  838. /**
  839. * Enumeration with all possible types for navigation failures. Can be passed to
  840. * {@link isNavigationFailure} to check for specific failures.
  841. */
  842. exports.NavigationFailureType = void 0;
  843. (function (NavigationFailureType) {
  844. /**
  845. * An aborted navigation is a navigation that failed because a navigation
  846. * guard returned `false` or called `next(false)`
  847. */
  848. NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";
  849. /**
  850. * A cancelled navigation is a navigation that failed because a more recent
  851. * navigation finished started (not necessarily finished).
  852. */
  853. NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";
  854. /**
  855. * A duplicated navigation is a navigation that failed because it was
  856. * initiated while already being at the exact same location.
  857. */
  858. NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";
  859. })(exports.NavigationFailureType || (exports.NavigationFailureType = {}));
  860. // DEV only debug messages
  861. const ErrorTypeMessages = {
  862. [1 /* ErrorTypes.MATCHER_NOT_FOUND */]({ location, currentLocation }) {
  863. return `No match for\n ${JSON.stringify(location)}${currentLocation
  864. ? '\nwhile being at\n' + JSON.stringify(currentLocation)
  865. : ''}`;
  866. },
  867. [2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
  868. return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
  869. },
  870. [4 /* ErrorTypes.NAVIGATION_ABORTED */]({ from, to }) {
  871. return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
  872. },
  873. [8 /* ErrorTypes.NAVIGATION_CANCELLED */]({ from, to }) {
  874. return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
  875. },
  876. [16 /* ErrorTypes.NAVIGATION_DUPLICATED */]({ from, to }) {
  877. return `Avoided redundant navigation to current location: "${from.fullPath}".`;
  878. },
  879. };
  880. /**
  881. * Creates a typed NavigationFailure object.
  882. * @internal
  883. * @param type - NavigationFailureType
  884. * @param params - { from, to }
  885. */
  886. function createRouterError(type, params) {
  887. // keep full error messages in cjs versions
  888. {
  889. return assign(new Error(ErrorTypeMessages[type](params)), {
  890. type,
  891. [NavigationFailureSymbol]: true,
  892. }, params);
  893. }
  894. }
  895. function isNavigationFailure(error, type) {
  896. return (error instanceof Error &&
  897. NavigationFailureSymbol in error &&
  898. (type == null || !!(error.type & type)));
  899. }
  900. const propertiesToLog = ['params', 'query', 'hash'];
  901. function stringifyRoute(to) {
  902. if (typeof to === 'string')
  903. return to;
  904. if (to.path != null)
  905. return to.path;
  906. const location = {};
  907. for (const key of propertiesToLog) {
  908. if (key in to)
  909. location[key] = to[key];
  910. }
  911. return JSON.stringify(location, null, 2);
  912. }
  913. // default pattern for a param: non-greedy everything but /
  914. const BASE_PARAM_PATTERN = '[^/]+?';
  915. const BASE_PATH_PARSER_OPTIONS = {
  916. sensitive: false,
  917. strict: false,
  918. start: true,
  919. end: true,
  920. };
  921. // Special Regex characters that must be escaped in static tokens
  922. const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
  923. /**
  924. * Creates a path parser from an array of Segments (a segment is an array of Tokens)
  925. *
  926. * @param segments - array of segments returned by tokenizePath
  927. * @param extraOptions - optional options for the regexp
  928. * @returns a PathParser
  929. */
  930. function tokensToParser(segments, extraOptions) {
  931. const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
  932. // the amount of scores is the same as the length of segments except for the root segment "/"
  933. const score = [];
  934. // the regexp as a string
  935. let pattern = options.start ? '^' : '';
  936. // extracted keys
  937. const keys = [];
  938. for (const segment of segments) {
  939. // the root segment needs special treatment
  940. const segmentScores = segment.length ? [] : [90 /* PathScore.Root */];
  941. // allow trailing slash
  942. if (options.strict && !segment.length)
  943. pattern += '/';
  944. for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
  945. const token = segment[tokenIndex];
  946. // resets the score if we are inside a sub-segment /:a-other-:b
  947. let subSegmentScore = 40 /* PathScore.Segment */ +
  948. (options.sensitive ? 0.25 /* PathScore.BonusCaseSensitive */ : 0);
  949. if (token.type === 0 /* TokenType.Static */) {
  950. // prepend the slash if we are starting a new segment
  951. if (!tokenIndex)
  952. pattern += '/';
  953. pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
  954. subSegmentScore += 40 /* PathScore.Static */;
  955. }
  956. else if (token.type === 1 /* TokenType.Param */) {
  957. const { value, repeatable, optional, regexp } = token;
  958. keys.push({
  959. name: value,
  960. repeatable,
  961. optional,
  962. });
  963. const re = regexp ? regexp : BASE_PARAM_PATTERN;
  964. // the user provided a custom regexp /:id(\\d+)
  965. if (re !== BASE_PARAM_PATTERN) {
  966. subSegmentScore += 10 /* PathScore.BonusCustomRegExp */;
  967. // make sure the regexp is valid before using it
  968. try {
  969. new RegExp(`(${re})`);
  970. }
  971. catch (err) {
  972. throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +
  973. err.message);
  974. }
  975. }
  976. // when we repeat we must take care of the repeating leading slash
  977. let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
  978. // prepend the slash if we are starting a new segment
  979. if (!tokenIndex)
  980. subPattern =
  981. // avoid an optional / if there are more segments e.g. /:p?-static
  982. // or /:p?-:p2
  983. optional && segment.length < 2
  984. ? `(?:/${subPattern})`
  985. : '/' + subPattern;
  986. if (optional)
  987. subPattern += '?';
  988. pattern += subPattern;
  989. subSegmentScore += 20 /* PathScore.Dynamic */;
  990. if (optional)
  991. subSegmentScore += -8 /* PathScore.BonusOptional */;
  992. if (repeatable)
  993. subSegmentScore += -20 /* PathScore.BonusRepeatable */;
  994. if (re === '.*')
  995. subSegmentScore += -50 /* PathScore.BonusWildcard */;
  996. }
  997. segmentScores.push(subSegmentScore);
  998. }
  999. // an empty array like /home/ -> [[{home}], []]
  1000. // if (!segment.length) pattern += '/'
  1001. score.push(segmentScores);
  1002. }
  1003. // only apply the strict bonus to the last score
  1004. if (options.strict && options.end) {
  1005. const i = score.length - 1;
  1006. score[i][score[i].length - 1] += 0.7000000000000001 /* PathScore.BonusStrict */;
  1007. }
  1008. // TODO: dev only warn double trailing slash
  1009. if (!options.strict)
  1010. pattern += '/?';
  1011. if (options.end)
  1012. pattern += '$';
  1013. // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
  1014. else if (options.strict && !pattern.endsWith('/'))
  1015. pattern += '(?:/|$)';
  1016. const re = new RegExp(pattern, options.sensitive ? '' : 'i');
  1017. function parse(path) {
  1018. const match = path.match(re);
  1019. const params = {};
  1020. if (!match)
  1021. return null;
  1022. for (let i = 1; i < match.length; i++) {
  1023. const value = match[i] || '';
  1024. const key = keys[i - 1];
  1025. params[key.name] = value && key.repeatable ? value.split('/') : value;
  1026. }
  1027. return params;
  1028. }
  1029. function stringify(params) {
  1030. let path = '';
  1031. // for optional parameters to allow to be empty
  1032. let avoidDuplicatedSlash = false;
  1033. for (const segment of segments) {
  1034. if (!avoidDuplicatedSlash || !path.endsWith('/'))
  1035. path += '/';
  1036. avoidDuplicatedSlash = false;
  1037. for (const token of segment) {
  1038. if (token.type === 0 /* TokenType.Static */) {
  1039. path += token.value;
  1040. }
  1041. else if (token.type === 1 /* TokenType.Param */) {
  1042. const { value, repeatable, optional } = token;
  1043. const param = value in params ? params[value] : '';
  1044. if (isArray(param) && !repeatable) {
  1045. throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
  1046. }
  1047. const text = isArray(param)
  1048. ? param.join('/')
  1049. : param;
  1050. if (!text) {
  1051. if (optional) {
  1052. // if we have more than one optional param like /:a?-static we don't need to care about the optional param
  1053. if (segment.length < 2) {
  1054. // remove the last slash as we could be at the end
  1055. if (path.endsWith('/'))
  1056. path = path.slice(0, -1);
  1057. // do not append a slash on the next iteration
  1058. else
  1059. avoidDuplicatedSlash = true;
  1060. }
  1061. }
  1062. else
  1063. throw new Error(`Missing required param "${value}"`);
  1064. }
  1065. path += text;
  1066. }
  1067. }
  1068. }
  1069. // avoid empty path when we have multiple optional params
  1070. return path || '/';
  1071. }
  1072. return {
  1073. re,
  1074. score,
  1075. keys,
  1076. parse,
  1077. stringify,
  1078. };
  1079. }
  1080. /**
  1081. * Compares an array of numbers as used in PathParser.score and returns a
  1082. * number. This function can be used to `sort` an array
  1083. *
  1084. * @param a - first array of numbers
  1085. * @param b - second array of numbers
  1086. * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
  1087. * should be sorted first
  1088. */
  1089. function compareScoreArray(a, b) {
  1090. let i = 0;
  1091. while (i < a.length && i < b.length) {
  1092. const diff = b[i] - a[i];
  1093. // only keep going if diff === 0
  1094. if (diff)
  1095. return diff;
  1096. i++;
  1097. }
  1098. // if the last subsegment was Static, the shorter segments should be sorted first
  1099. // otherwise sort the longest segment first
  1100. if (a.length < b.length) {
  1101. return a.length === 1 && a[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
  1102. ? -1
  1103. : 1;
  1104. }
  1105. else if (a.length > b.length) {
  1106. return b.length === 1 && b[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
  1107. ? 1
  1108. : -1;
  1109. }
  1110. return 0;
  1111. }
  1112. /**
  1113. * Compare function that can be used with `sort` to sort an array of PathParser
  1114. *
  1115. * @param a - first PathParser
  1116. * @param b - second PathParser
  1117. * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
  1118. */
  1119. function comparePathParserScore(a, b) {
  1120. let i = 0;
  1121. const aScore = a.score;
  1122. const bScore = b.score;
  1123. while (i < aScore.length && i < bScore.length) {
  1124. const comp = compareScoreArray(aScore[i], bScore[i]);
  1125. // do not return if both are equal
  1126. if (comp)
  1127. return comp;
  1128. i++;
  1129. }
  1130. if (Math.abs(bScore.length - aScore.length) === 1) {
  1131. if (isLastScoreNegative(aScore))
  1132. return 1;
  1133. if (isLastScoreNegative(bScore))
  1134. return -1;
  1135. }
  1136. // if a and b share the same score entries but b has more, sort b first
  1137. return bScore.length - aScore.length;
  1138. // this is the ternary version
  1139. // return aScore.length < bScore.length
  1140. // ? 1
  1141. // : aScore.length > bScore.length
  1142. // ? -1
  1143. // : 0
  1144. }
  1145. /**
  1146. * This allows detecting splats at the end of a path: /home/:id(.*)*
  1147. *
  1148. * @param score - score to check
  1149. * @returns true if the last entry is negative
  1150. */
  1151. function isLastScoreNegative(score) {
  1152. const last = score[score.length - 1];
  1153. return score.length > 0 && last[last.length - 1] < 0;
  1154. }
  1155. const ROOT_TOKEN = {
  1156. type: 0 /* TokenType.Static */,
  1157. value: '',
  1158. };
  1159. const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
  1160. // After some profiling, the cache seems to be unnecessary because tokenizePath
  1161. // (the slowest part of adding a route) is very fast
  1162. // const tokenCache = new Map<string, Token[][]>()
  1163. function tokenizePath(path) {
  1164. if (!path)
  1165. return [[]];
  1166. if (path === '/')
  1167. return [[ROOT_TOKEN]];
  1168. if (!path.startsWith('/')) {
  1169. throw new Error(`Invalid path "${path}"`);
  1170. }
  1171. // if (tokenCache.has(path)) return tokenCache.get(path)!
  1172. function crash(message) {
  1173. throw new Error(`ERR (${state})/"${buffer}": ${message}`);
  1174. }
  1175. let state = 0 /* TokenizerState.Static */;
  1176. let previousState = state;
  1177. const tokens = [];
  1178. // the segment will always be valid because we get into the initial state
  1179. // with the leading /
  1180. let segment;
  1181. function finalizeSegment() {
  1182. if (segment)
  1183. tokens.push(segment);
  1184. segment = [];
  1185. }
  1186. // index on the path
  1187. let i = 0;
  1188. // char at index
  1189. let char;
  1190. // buffer of the value read
  1191. let buffer = '';
  1192. // custom regexp for a param
  1193. let customRe = '';
  1194. function consumeBuffer() {
  1195. if (!buffer)
  1196. return;
  1197. if (state === 0 /* TokenizerState.Static */) {
  1198. segment.push({
  1199. type: 0 /* TokenType.Static */,
  1200. value: buffer,
  1201. });
  1202. }
  1203. else if (state === 1 /* TokenizerState.Param */ ||
  1204. state === 2 /* TokenizerState.ParamRegExp */ ||
  1205. state === 3 /* TokenizerState.ParamRegExpEnd */) {
  1206. if (segment.length > 1 && (char === '*' || char === '+'))
  1207. crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
  1208. segment.push({
  1209. type: 1 /* TokenType.Param */,
  1210. value: buffer,
  1211. regexp: customRe,
  1212. repeatable: char === '*' || char === '+',
  1213. optional: char === '*' || char === '?',
  1214. });
  1215. }
  1216. else {
  1217. crash('Invalid state to consume buffer');
  1218. }
  1219. buffer = '';
  1220. }
  1221. function addCharToBuffer() {
  1222. buffer += char;
  1223. }
  1224. while (i < path.length) {
  1225. char = path[i++];
  1226. if (char === '\\' && state !== 2 /* TokenizerState.ParamRegExp */) {
  1227. previousState = state;
  1228. state = 4 /* TokenizerState.EscapeNext */;
  1229. continue;
  1230. }
  1231. switch (state) {
  1232. case 0 /* TokenizerState.Static */:
  1233. if (char === '/') {
  1234. if (buffer) {
  1235. consumeBuffer();
  1236. }
  1237. finalizeSegment();
  1238. }
  1239. else if (char === ':') {
  1240. consumeBuffer();
  1241. state = 1 /* TokenizerState.Param */;
  1242. }
  1243. else {
  1244. addCharToBuffer();
  1245. }
  1246. break;
  1247. case 4 /* TokenizerState.EscapeNext */:
  1248. addCharToBuffer();
  1249. state = previousState;
  1250. break;
  1251. case 1 /* TokenizerState.Param */:
  1252. if (char === '(') {
  1253. state = 2 /* TokenizerState.ParamRegExp */;
  1254. }
  1255. else if (VALID_PARAM_RE.test(char)) {
  1256. addCharToBuffer();
  1257. }
  1258. else {
  1259. consumeBuffer();
  1260. state = 0 /* TokenizerState.Static */;
  1261. // go back one character if we were not modifying
  1262. if (char !== '*' && char !== '?' && char !== '+')
  1263. i--;
  1264. }
  1265. break;
  1266. case 2 /* TokenizerState.ParamRegExp */:
  1267. // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
  1268. // it already works by escaping the closing )
  1269. // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
  1270. // is this really something people need since you can also write
  1271. // /prefix_:p()_suffix
  1272. if (char === ')') {
  1273. // handle the escaped )
  1274. if (customRe[customRe.length - 1] == '\\')
  1275. customRe = customRe.slice(0, -1) + char;
  1276. else
  1277. state = 3 /* TokenizerState.ParamRegExpEnd */;
  1278. }
  1279. else {
  1280. customRe += char;
  1281. }
  1282. break;
  1283. case 3 /* TokenizerState.ParamRegExpEnd */:
  1284. // same as finalizing a param
  1285. consumeBuffer();
  1286. state = 0 /* TokenizerState.Static */;
  1287. // go back one character if we were not modifying
  1288. if (char !== '*' && char !== '?' && char !== '+')
  1289. i--;
  1290. customRe = '';
  1291. break;
  1292. default:
  1293. crash('Unknown state');
  1294. break;
  1295. }
  1296. }
  1297. if (state === 2 /* TokenizerState.ParamRegExp */)
  1298. crash(`Unfinished custom RegExp for param "${buffer}"`);
  1299. consumeBuffer();
  1300. finalizeSegment();
  1301. // tokenCache.set(path, tokens)
  1302. return tokens;
  1303. }
  1304. function createRouteRecordMatcher(record, parent, options) {
  1305. const parser = tokensToParser(tokenizePath(record.path), options);
  1306. const matcher = assign(parser, {
  1307. record,
  1308. parent,
  1309. // these needs to be populated by the parent
  1310. children: [],
  1311. alias: [],
  1312. });
  1313. if (parent) {
  1314. // both are aliases or both are not aliases
  1315. // we don't want to mix them because the order is used when
  1316. // passing originalRecord in Matcher.addRoute
  1317. if (!matcher.record.aliasOf === !parent.record.aliasOf)
  1318. parent.children.push(matcher);
  1319. }
  1320. return matcher;
  1321. }
  1322. /**
  1323. * Creates a Router Matcher.
  1324. *
  1325. * @internal
  1326. * @param routes - array of initial routes
  1327. * @param globalOptions - global route options
  1328. */
  1329. function createRouterMatcher(routes, globalOptions) {
  1330. // normalized ordered array of matchers
  1331. const matchers = [];
  1332. const matcherMap = new Map();
  1333. globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);
  1334. function getRecordMatcher(name) {
  1335. return matcherMap.get(name);
  1336. }
  1337. function addRoute(record, parent, originalRecord) {
  1338. // used later on to remove by name
  1339. const isRootAdd = !originalRecord;
  1340. const mainNormalizedRecord = normalizeRouteRecord(record);
  1341. // we might be the child of an alias
  1342. mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
  1343. const options = mergeOptions(globalOptions, record);
  1344. // generate an array of records to correctly handle aliases
  1345. const normalizedRecords = [mainNormalizedRecord];
  1346. if ('alias' in record) {
  1347. const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;
  1348. for (const alias of aliases) {
  1349. normalizedRecords.push(
  1350. // we need to normalize again to ensure the `mods` property
  1351. // being non enumerable
  1352. normalizeRouteRecord(assign({}, mainNormalizedRecord, {
  1353. // this allows us to hold a copy of the `components` option
  1354. // so that async components cache is hold on the original record
  1355. components: originalRecord
  1356. ? originalRecord.record.components
  1357. : mainNormalizedRecord.components,
  1358. path: alias,
  1359. // we might be the child of an alias
  1360. aliasOf: originalRecord
  1361. ? originalRecord.record
  1362. : mainNormalizedRecord,
  1363. // the aliases are always of the same kind as the original since they
  1364. // are defined on the same record
  1365. })));
  1366. }
  1367. }
  1368. let matcher;
  1369. let originalMatcher;
  1370. for (const normalizedRecord of normalizedRecords) {
  1371. const { path } = normalizedRecord;
  1372. // Build up the path for nested routes if the child isn't an absolute
  1373. // route. Only add the / delimiter if the child path isn't empty and if the
  1374. // parent path doesn't have a trailing slash
  1375. if (parent && path[0] !== '/') {
  1376. const parentPath = parent.record.path;
  1377. const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
  1378. normalizedRecord.path =
  1379. parent.record.path + (path && connectingSlash + path);
  1380. }
  1381. // create the object beforehand, so it can be passed to children
  1382. matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
  1383. // if we are an alias we must tell the original record that we exist,
  1384. // so we can be removed
  1385. if (originalRecord) {
  1386. originalRecord.alias.push(matcher);
  1387. }
  1388. else {
  1389. // otherwise, the first record is the original and others are aliases
  1390. originalMatcher = originalMatcher || matcher;
  1391. if (originalMatcher !== matcher)
  1392. originalMatcher.alias.push(matcher);
  1393. // remove the route if named and only for the top record (avoid in nested calls)
  1394. // this works because the original record is the first one
  1395. if (isRootAdd && record.name && !isAliasRecord(matcher)) {
  1396. removeRoute(record.name);
  1397. }
  1398. }
  1399. // Avoid adding a record that doesn't display anything. This allows passing through records without a component to
  1400. // not be reached and pass through the catch all route
  1401. if (isMatchable(matcher)) {
  1402. insertMatcher(matcher);
  1403. }
  1404. if (mainNormalizedRecord.children) {
  1405. const children = mainNormalizedRecord.children;
  1406. for (let i = 0; i < children.length; i++) {
  1407. addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
  1408. }
  1409. }
  1410. // if there was no original record, then the first one was not an alias and all
  1411. // other aliases (if any) need to reference this record when adding children
  1412. originalRecord = originalRecord || matcher;
  1413. // TODO: add normalized records for more flexibility
  1414. // if (parent && isAliasRecord(originalRecord)) {
  1415. // parent.children.push(originalRecord)
  1416. // }
  1417. }
  1418. return originalMatcher
  1419. ? () => {
  1420. // since other matchers are aliases, they should be removed by the original matcher
  1421. removeRoute(originalMatcher);
  1422. }
  1423. : noop;
  1424. }
  1425. function removeRoute(matcherRef) {
  1426. if (isRouteName(matcherRef)) {
  1427. const matcher = matcherMap.get(matcherRef);
  1428. if (matcher) {
  1429. matcherMap.delete(matcherRef);
  1430. matchers.splice(matchers.indexOf(matcher), 1);
  1431. matcher.children.forEach(removeRoute);
  1432. matcher.alias.forEach(removeRoute);
  1433. }
  1434. }
  1435. else {
  1436. const index = matchers.indexOf(matcherRef);
  1437. if (index > -1) {
  1438. matchers.splice(index, 1);
  1439. if (matcherRef.record.name)
  1440. matcherMap.delete(matcherRef.record.name);
  1441. matcherRef.children.forEach(removeRoute);
  1442. matcherRef.alias.forEach(removeRoute);
  1443. }
  1444. }
  1445. }
  1446. function getRoutes() {
  1447. return matchers;
  1448. }
  1449. function insertMatcher(matcher) {
  1450. const index = findInsertionIndex(matcher, matchers);
  1451. matchers.splice(index, 0, matcher);
  1452. // only add the original record to the name map
  1453. if (matcher.record.name && !isAliasRecord(matcher))
  1454. matcherMap.set(matcher.record.name, matcher);
  1455. }
  1456. function resolve(location, currentLocation) {
  1457. let matcher;
  1458. let params = {};
  1459. let path;
  1460. let name;
  1461. if ('name' in location && location.name) {
  1462. matcher = matcherMap.get(location.name);
  1463. if (!matcher)
  1464. throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
  1465. location,
  1466. });
  1467. name = matcher.record.name;
  1468. params = assign(
  1469. // paramsFromLocation is a new object
  1470. paramsFromLocation(currentLocation.params,
  1471. // only keep params that exist in the resolved location
  1472. // only keep optional params coming from a parent record
  1473. matcher.keys
  1474. .filter(k => !k.optional)
  1475. .concat(matcher.parent ? matcher.parent.keys.filter(k => k.optional) : [])
  1476. .map(k => k.name)),
  1477. // discard any existing params in the current location that do not exist here
  1478. // #1497 this ensures better active/exact matching
  1479. location.params &&
  1480. paramsFromLocation(location.params, matcher.keys.map(k => k.name)));
  1481. // throws if cannot be stringified
  1482. path = matcher.stringify(params);
  1483. }
  1484. else if (location.path != null) {
  1485. // no need to resolve the path with the matcher as it was provided
  1486. // this also allows the user to control the encoding
  1487. path = location.path;
  1488. matcher = matchers.find(m => m.re.test(path));
  1489. // matcher should have a value after the loop
  1490. if (matcher) {
  1491. // we know the matcher works because we tested the regexp
  1492. params = matcher.parse(path);
  1493. name = matcher.record.name;
  1494. }
  1495. // location is a relative path
  1496. }
  1497. else {
  1498. // match by name or path of current route
  1499. matcher = currentLocation.name
  1500. ? matcherMap.get(currentLocation.name)
  1501. : matchers.find(m => m.re.test(currentLocation.path));
  1502. if (!matcher)
  1503. throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
  1504. location,
  1505. currentLocation,
  1506. });
  1507. name = matcher.record.name;
  1508. // since we are navigating to the same location, we don't need to pick the
  1509. // params like when `name` is provided
  1510. params = assign({}, currentLocation.params, location.params);
  1511. path = matcher.stringify(params);
  1512. }
  1513. const matched = [];
  1514. let parentMatcher = matcher;
  1515. while (parentMatcher) {
  1516. // reversed order so parents are at the beginning
  1517. matched.unshift(parentMatcher.record);
  1518. parentMatcher = parentMatcher.parent;
  1519. }
  1520. return {
  1521. name,
  1522. path,
  1523. params,
  1524. matched,
  1525. meta: mergeMetaFields(matched),
  1526. };
  1527. }
  1528. // add initial routes
  1529. routes.forEach(route => addRoute(route));
  1530. function clearRoutes() {
  1531. matchers.length = 0;
  1532. matcherMap.clear();
  1533. }
  1534. return {
  1535. addRoute,
  1536. resolve,
  1537. removeRoute,
  1538. clearRoutes,
  1539. getRoutes,
  1540. getRecordMatcher,
  1541. };
  1542. }
  1543. function paramsFromLocation(params, keys) {
  1544. const newParams = {};
  1545. for (const key of keys) {
  1546. if (key in params)
  1547. newParams[key] = params[key];
  1548. }
  1549. return newParams;
  1550. }
  1551. /**
  1552. * Normalizes a RouteRecordRaw. Creates a copy
  1553. *
  1554. * @param record
  1555. * @returns the normalized version
  1556. */
  1557. function normalizeRouteRecord(record) {
  1558. const normalized = {
  1559. path: record.path,
  1560. redirect: record.redirect,
  1561. name: record.name,
  1562. meta: record.meta || {},
  1563. aliasOf: record.aliasOf,
  1564. beforeEnter: record.beforeEnter,
  1565. props: normalizeRecordProps(record),
  1566. children: record.children || [],
  1567. instances: {},
  1568. leaveGuards: new Set(),
  1569. updateGuards: new Set(),
  1570. enterCallbacks: {},
  1571. // must be declared afterwards
  1572. // mods: {},
  1573. components: 'components' in record
  1574. ? record.components || null
  1575. : record.component && { default: record.component },
  1576. };
  1577. // mods contain modules and shouldn't be copied,
  1578. // logged or anything. It's just used for internal
  1579. // advanced use cases like data loaders
  1580. Object.defineProperty(normalized, 'mods', {
  1581. value: {},
  1582. });
  1583. return normalized;
  1584. }
  1585. /**
  1586. * Normalize the optional `props` in a record to always be an object similar to
  1587. * components. Also accept a boolean for components.
  1588. * @param record
  1589. */
  1590. function normalizeRecordProps(record) {
  1591. const propsObject = {};
  1592. // props does not exist on redirect records, but we can set false directly
  1593. const props = record.props || false;
  1594. if ('component' in record) {
  1595. propsObject.default = props;
  1596. }
  1597. else {
  1598. // NOTE: we could also allow a function to be applied to every component.
  1599. // Would need user feedback for use cases
  1600. for (const name in record.components)
  1601. propsObject[name] = typeof props === 'object' ? props[name] : props;
  1602. }
  1603. return propsObject;
  1604. }
  1605. /**
  1606. * Checks if a record or any of its parent is an alias
  1607. * @param record
  1608. */
  1609. function isAliasRecord(record) {
  1610. while (record) {
  1611. if (record.record.aliasOf)
  1612. return true;
  1613. record = record.parent;
  1614. }
  1615. return false;
  1616. }
  1617. /**
  1618. * Merge meta fields of an array of records
  1619. *
  1620. * @param matched - array of matched records
  1621. */
  1622. function mergeMetaFields(matched) {
  1623. return matched.reduce((meta, record) => assign(meta, record.meta), {});
  1624. }
  1625. function mergeOptions(defaults, partialOptions) {
  1626. const options = {};
  1627. for (const key in defaults) {
  1628. options[key] = key in partialOptions ? partialOptions[key] : defaults[key];
  1629. }
  1630. return options;
  1631. }
  1632. /**
  1633. * Performs a binary search to find the correct insertion index for a new matcher.
  1634. *
  1635. * Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
  1636. * with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
  1637. *
  1638. * @param matcher - new matcher to be inserted
  1639. * @param matchers - existing matchers
  1640. */
  1641. function findInsertionIndex(matcher, matchers) {
  1642. // First phase: binary search based on score
  1643. let lower = 0;
  1644. let upper = matchers.length;
  1645. while (lower !== upper) {
  1646. const mid = (lower + upper) >> 1;
  1647. const sortOrder = comparePathParserScore(matcher, matchers[mid]);
  1648. if (sortOrder < 0) {
  1649. upper = mid;
  1650. }
  1651. else {
  1652. lower = mid + 1;
  1653. }
  1654. }
  1655. // Second phase: check for an ancestor with the same score
  1656. const insertionAncestor = getInsertionAncestor(matcher);
  1657. if (insertionAncestor) {
  1658. upper = matchers.lastIndexOf(insertionAncestor, upper - 1);
  1659. }
  1660. return upper;
  1661. }
  1662. function getInsertionAncestor(matcher) {
  1663. let ancestor = matcher;
  1664. while ((ancestor = ancestor.parent)) {
  1665. if (isMatchable(ancestor) &&
  1666. comparePathParserScore(matcher, ancestor) === 0) {
  1667. return ancestor;
  1668. }
  1669. }
  1670. return;
  1671. }
  1672. /**
  1673. * Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without
  1674. * a component, or name, or redirect, are just used to group other routes.
  1675. * @param matcher
  1676. * @param matcher.record record of the matcher
  1677. * @returns
  1678. */
  1679. function isMatchable({ record }) {
  1680. return !!(record.name ||
  1681. (record.components && Object.keys(record.components).length) ||
  1682. record.redirect);
  1683. }
  1684. /**
  1685. * Transforms a queryString into a {@link LocationQuery} object. Accept both, a
  1686. * version with the leading `?` and without Should work as URLSearchParams
  1687. * @internal
  1688. *
  1689. * @param search - search string to parse
  1690. * @returns a query object
  1691. */
  1692. function parseQuery(search) {
  1693. const query = {};
  1694. // avoid creating an object with an empty key and empty value
  1695. // because of split('&')
  1696. if (search === '' || search === '?')
  1697. return query;
  1698. const hasLeadingIM = search[0] === '?';
  1699. const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');
  1700. for (let i = 0; i < searchParams.length; ++i) {
  1701. // pre decode the + into space
  1702. const searchParam = searchParams[i].replace(PLUS_RE, ' ');
  1703. // allow the = character
  1704. const eqPos = searchParam.indexOf('=');
  1705. const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
  1706. const value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));
  1707. if (key in query) {
  1708. // an extra variable for ts types
  1709. let currentValue = query[key];
  1710. if (!isArray(currentValue)) {
  1711. currentValue = query[key] = [currentValue];
  1712. }
  1713. currentValue.push(value);
  1714. }
  1715. else {
  1716. query[key] = value;
  1717. }
  1718. }
  1719. return query;
  1720. }
  1721. /**
  1722. * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it
  1723. * doesn't prepend a `?`
  1724. *
  1725. * @internal
  1726. *
  1727. * @param query - query object to stringify
  1728. * @returns string version of the query without the leading `?`
  1729. */
  1730. function stringifyQuery(query) {
  1731. let search = '';
  1732. for (let key in query) {
  1733. const value = query[key];
  1734. key = encodeQueryKey(key);
  1735. if (value == null) {
  1736. // only null adds the value
  1737. if (value !== undefined) {
  1738. search += (search.length ? '&' : '') + key;
  1739. }
  1740. continue;
  1741. }
  1742. // keep null values
  1743. const values = isArray(value)
  1744. ? value.map(v => v && encodeQueryValue(v))
  1745. : [value && encodeQueryValue(value)];
  1746. values.forEach(value => {
  1747. // skip undefined values in arrays as if they were not present
  1748. // smaller code than using filter
  1749. if (value !== undefined) {
  1750. // only append & with non-empty search
  1751. search += (search.length ? '&' : '') + key;
  1752. if (value != null)
  1753. search += '=' + value;
  1754. }
  1755. });
  1756. }
  1757. return search;
  1758. }
  1759. /**
  1760. * Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting
  1761. * numbers into strings, removing keys with an undefined value and replacing
  1762. * undefined with null in arrays
  1763. *
  1764. * @param query - query object to normalize
  1765. * @returns a normalized query object
  1766. */
  1767. function normalizeQuery(query) {
  1768. const normalizedQuery = {};
  1769. for (const key in query) {
  1770. const value = query[key];
  1771. if (value !== undefined) {
  1772. normalizedQuery[key] = isArray(value)
  1773. ? value.map(v => (v == null ? null : '' + v))
  1774. : value == null
  1775. ? value
  1776. : '' + value;
  1777. }
  1778. }
  1779. return normalizedQuery;
  1780. }
  1781. /**
  1782. * RouteRecord being rendered by the closest ancestor Router View. Used for
  1783. * `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
  1784. * Location Matched
  1785. *
  1786. * @internal
  1787. */
  1788. const matchedRouteKey = Symbol('');
  1789. /**
  1790. * Allows overriding the router view depth to control which component in
  1791. * `matched` is rendered. rvd stands for Router View Depth
  1792. *
  1793. * @internal
  1794. */
  1795. const viewDepthKey = Symbol('');
  1796. /**
  1797. * Allows overriding the router instance returned by `useRouter` in tests. r
  1798. * stands for router
  1799. *
  1800. * @internal
  1801. */
  1802. const routerKey = Symbol('');
  1803. /**
  1804. * Allows overriding the current route returned by `useRoute` in tests. rl
  1805. * stands for route location
  1806. *
  1807. * @internal
  1808. */
  1809. const routeLocationKey = Symbol('');
  1810. /**
  1811. * Allows overriding the current route used by router-view. Internally this is
  1812. * used when the `route` prop is passed.
  1813. *
  1814. * @internal
  1815. */
  1816. const routerViewLocationKey = Symbol('');
  1817. /**
  1818. * Create a list of callbacks that can be reset. Used to create before and after navigation guards list
  1819. */
  1820. function useCallbacks() {
  1821. let handlers = [];
  1822. function add(handler) {
  1823. handlers.push(handler);
  1824. return () => {
  1825. const i = handlers.indexOf(handler);
  1826. if (i > -1)
  1827. handlers.splice(i, 1);
  1828. };
  1829. }
  1830. function reset() {
  1831. handlers = [];
  1832. }
  1833. return {
  1834. add,
  1835. list: () => handlers.slice(),
  1836. reset,
  1837. };
  1838. }
  1839. function registerGuard(record, name, guard) {
  1840. const removeFromList = () => {
  1841. record[name].delete(guard);
  1842. };
  1843. vue.onUnmounted(removeFromList);
  1844. vue.onDeactivated(removeFromList);
  1845. vue.onActivated(() => {
  1846. record[name].add(guard);
  1847. });
  1848. record[name].add(guard);
  1849. }
  1850. /**
  1851. * Add a navigation guard that triggers whenever the component for the current
  1852. * location is about to be left. Similar to {@link beforeRouteLeave} but can be
  1853. * used in any component. The guard is removed when the component is unmounted.
  1854. *
  1855. * @param leaveGuard - {@link NavigationGuard}
  1856. */
  1857. function onBeforeRouteLeave(leaveGuard) {
  1858. const activeRecord = vue.inject(matchedRouteKey,
  1859. // to avoid warning
  1860. {}).value;
  1861. if (!activeRecord) {
  1862. return;
  1863. }
  1864. registerGuard(activeRecord, 'leaveGuards', leaveGuard);
  1865. }
  1866. /**
  1867. * Add a navigation guard that triggers whenever the current location is about
  1868. * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any
  1869. * component. The guard is removed when the component is unmounted.
  1870. *
  1871. * @param updateGuard - {@link NavigationGuard}
  1872. */
  1873. function onBeforeRouteUpdate(updateGuard) {
  1874. const activeRecord = vue.inject(matchedRouteKey,
  1875. // to avoid warning
  1876. {}).value;
  1877. if (!activeRecord) {
  1878. return;
  1879. }
  1880. registerGuard(activeRecord, 'updateGuards', updateGuard);
  1881. }
  1882. function guardToPromiseFn(guard, to, from, record, name, runWithContext = fn => fn()) {
  1883. // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
  1884. const enterCallbackArray = record &&
  1885. // name is defined if record is because of the function overload
  1886. (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
  1887. return () => new Promise((resolve, reject) => {
  1888. const next = (valid) => {
  1889. if (valid === false) {
  1890. reject(createRouterError(4 /* ErrorTypes.NAVIGATION_ABORTED */, {
  1891. from,
  1892. to,
  1893. }));
  1894. }
  1895. else if (valid instanceof Error) {
  1896. reject(valid);
  1897. }
  1898. else if (isRouteLocation(valid)) {
  1899. reject(createRouterError(2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */, {
  1900. from: to,
  1901. to: valid,
  1902. }));
  1903. }
  1904. else {
  1905. if (enterCallbackArray &&
  1906. // since enterCallbackArray is truthy, both record and name also are
  1907. record.enterCallbacks[name] === enterCallbackArray &&
  1908. typeof valid === 'function') {
  1909. enterCallbackArray.push(valid);
  1910. }
  1911. resolve();
  1912. }
  1913. };
  1914. // wrapping with Promise.resolve allows it to work with both async and sync guards
  1915. const guardReturn = runWithContext(() => guard.call(record && record.instances[name], to, from, next));
  1916. let guardCall = Promise.resolve(guardReturn);
  1917. if (guard.length < 3)
  1918. guardCall = guardCall.then(next);
  1919. guardCall.catch(err => reject(err));
  1920. });
  1921. }
  1922. function extractComponentsGuards(matched, guardType, to, from, runWithContext = fn => fn()) {
  1923. const guards = [];
  1924. for (const record of matched) {
  1925. for (const name in record.components) {
  1926. let rawComponent = record.components[name];
  1927. // skip update and leave guards if the route component is not mounted
  1928. if (guardType !== 'beforeRouteEnter' && !record.instances[name])
  1929. continue;
  1930. if (isRouteComponent(rawComponent)) {
  1931. // __vccOpts is added by vue-class-component and contain the regular options
  1932. const options = rawComponent.__vccOpts || rawComponent;
  1933. const guard = options[guardType];
  1934. guard &&
  1935. guards.push(guardToPromiseFn(guard, to, from, record, name, runWithContext));
  1936. }
  1937. else {
  1938. // start requesting the chunk already
  1939. let componentPromise = rawComponent();
  1940. guards.push(() => componentPromise.then(resolved => {
  1941. if (!resolved)
  1942. throw new Error(`Couldn't resolve component "${name}" at "${record.path}"`);
  1943. const resolvedComponent = isESModule(resolved)
  1944. ? resolved.default
  1945. : resolved;
  1946. // keep the resolved module for plugins like data loaders
  1947. record.mods[name] = resolved;
  1948. // replace the function with the resolved component
  1949. // cannot be null or undefined because we went into the for loop
  1950. record.components[name] = resolvedComponent;
  1951. // __vccOpts is added by vue-class-component and contain the regular options
  1952. const options = resolvedComponent.__vccOpts || resolvedComponent;
  1953. const guard = options[guardType];
  1954. return (guard &&
  1955. guardToPromiseFn(guard, to, from, record, name, runWithContext)());
  1956. }));
  1957. }
  1958. }
  1959. }
  1960. return guards;
  1961. }
  1962. /**
  1963. * Ensures a route is loaded, so it can be passed as o prop to `<RouterView>`.
  1964. *
  1965. * @param route - resolved route to load
  1966. */
  1967. function loadRouteLocation(route) {
  1968. return route.matched.every(record => record.redirect)
  1969. ? Promise.reject(new Error('Cannot load a route that redirects.'))
  1970. : Promise.all(route.matched.map(record => record.components &&
  1971. Promise.all(Object.keys(record.components).reduce((promises, name) => {
  1972. const rawComponent = record.components[name];
  1973. if (typeof rawComponent === 'function' &&
  1974. !('displayName' in rawComponent)) {
  1975. promises.push(rawComponent().then(resolved => {
  1976. if (!resolved)
  1977. return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.`));
  1978. const resolvedComponent = isESModule(resolved)
  1979. ? resolved.default
  1980. : resolved;
  1981. // keep the resolved module for plugins like data loaders
  1982. record.mods[name] = resolved;
  1983. // replace the function with the resolved component
  1984. // cannot be null or undefined because we went into the for loop
  1985. record.components[name] = resolvedComponent;
  1986. return;
  1987. }));
  1988. }
  1989. return promises;
  1990. }, [])))).then(() => route);
  1991. }
  1992. // TODO: we could allow currentRoute as a prop to expose `isActive` and
  1993. // `isExactActive` behavior should go through an RFC
  1994. /**
  1995. * Returns the internal behavior of a {@link RouterLink} without the rendering part.
  1996. *
  1997. * @param props - a `to` location and an optional `replace` flag
  1998. */
  1999. function useLink(props) {
  2000. const router = vue.inject(routerKey);
  2001. const currentRoute = vue.inject(routeLocationKey);
  2002. const route = vue.computed(() => {
  2003. const to = vue.unref(props.to);
  2004. return router.resolve(to);
  2005. });
  2006. const activeRecordIndex = vue.computed(() => {
  2007. const { matched } = route.value;
  2008. const { length } = matched;
  2009. const routeMatched = matched[length - 1];
  2010. const currentMatched = currentRoute.matched;
  2011. if (!routeMatched || !currentMatched.length)
  2012. return -1;
  2013. const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));
  2014. if (index > -1)
  2015. return index;
  2016. // possible parent record
  2017. const parentRecordPath = getOriginalPath(matched[length - 2]);
  2018. return (
  2019. // we are dealing with nested routes
  2020. length > 1 &&
  2021. // if the parent and matched route have the same path, this link is
  2022. // referring to the empty child. Or we currently are on a different
  2023. // child of the same parent
  2024. getOriginalPath(routeMatched) === parentRecordPath &&
  2025. // avoid comparing the child with its parent
  2026. currentMatched[currentMatched.length - 1].path !== parentRecordPath
  2027. ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))
  2028. : index);
  2029. });
  2030. const isActive = vue.computed(() => activeRecordIndex.value > -1 &&
  2031. includesParams(currentRoute.params, route.value.params));
  2032. const isExactActive = vue.computed(() => activeRecordIndex.value > -1 &&
  2033. activeRecordIndex.value === currentRoute.matched.length - 1 &&
  2034. isSameRouteLocationParams(currentRoute.params, route.value.params));
  2035. function navigate(e = {}) {
  2036. if (guardEvent(e)) {
  2037. const p = router[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to)
  2038. // avoid uncaught errors are they are logged anyway
  2039. ).catch(noop);
  2040. if (props.viewTransition &&
  2041. typeof document !== 'undefined' &&
  2042. 'startViewTransition' in document) {
  2043. document.startViewTransition(() => p);
  2044. }
  2045. return p;
  2046. }
  2047. return Promise.resolve();
  2048. }
  2049. /**
  2050. * NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
  2051. */
  2052. return {
  2053. route,
  2054. href: vue.computed(() => route.value.href),
  2055. isActive,
  2056. isExactActive,
  2057. navigate,
  2058. };
  2059. }
  2060. function preferSingleVNode(vnodes) {
  2061. return vnodes.length === 1 ? vnodes[0] : vnodes;
  2062. }
  2063. const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({
  2064. name: 'RouterLink',
  2065. compatConfig: { MODE: 3 },
  2066. props: {
  2067. to: {
  2068. type: [String, Object],
  2069. required: true,
  2070. },
  2071. replace: Boolean,
  2072. activeClass: String,
  2073. // inactiveClass: String,
  2074. exactActiveClass: String,
  2075. custom: Boolean,
  2076. ariaCurrentValue: {
  2077. type: String,
  2078. default: 'page',
  2079. },
  2080. },
  2081. useLink,
  2082. setup(props, { slots }) {
  2083. const link = vue.reactive(useLink(props));
  2084. const { options } = vue.inject(routerKey);
  2085. const elClass = vue.computed(() => ({
  2086. [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,
  2087. // [getLinkClass(
  2088. // props.inactiveClass,
  2089. // options.linkInactiveClass,
  2090. // 'router-link-inactive'
  2091. // )]: !link.isExactActive,
  2092. [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,
  2093. }));
  2094. return () => {
  2095. const children = slots.default && preferSingleVNode(slots.default(link));
  2096. return props.custom
  2097. ? children
  2098. : vue.h('a', {
  2099. 'aria-current': link.isExactActive
  2100. ? props.ariaCurrentValue
  2101. : null,
  2102. href: link.href,
  2103. // this would override user added attrs but Vue will still add
  2104. // the listener, so we end up triggering both
  2105. onClick: link.navigate,
  2106. class: elClass.value,
  2107. }, children);
  2108. };
  2109. },
  2110. });
  2111. // export the public type for h/tsx inference
  2112. // also to avoid inline import() in generated d.ts files
  2113. /**
  2114. * Component to render a link that triggers a navigation on click.
  2115. */
  2116. const RouterLink = RouterLinkImpl;
  2117. function guardEvent(e) {
  2118. // don't redirect with control keys
  2119. if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
  2120. return;
  2121. // don't redirect when preventDefault called
  2122. if (e.defaultPrevented)
  2123. return;
  2124. // don't redirect on right click
  2125. if (e.button !== undefined && e.button !== 0)
  2126. return;
  2127. // don't redirect if `target="_blank"`
  2128. // @ts-expect-error getAttribute does exist
  2129. if (e.currentTarget && e.currentTarget.getAttribute) {
  2130. // @ts-expect-error getAttribute exists
  2131. const target = e.currentTarget.getAttribute('target');
  2132. if (/\b_blank\b/i.test(target))
  2133. return;
  2134. }
  2135. // this may be a Weex event which doesn't have this method
  2136. if (e.preventDefault)
  2137. e.preventDefault();
  2138. return true;
  2139. }
  2140. function includesParams(outer, inner) {
  2141. for (const key in inner) {
  2142. const innerValue = inner[key];
  2143. const outerValue = outer[key];
  2144. if (typeof innerValue === 'string') {
  2145. if (innerValue !== outerValue)
  2146. return false;
  2147. }
  2148. else {
  2149. if (!isArray(outerValue) ||
  2150. outerValue.length !== innerValue.length ||
  2151. innerValue.some((value, i) => value !== outerValue[i]))
  2152. return false;
  2153. }
  2154. }
  2155. return true;
  2156. }
  2157. /**
  2158. * Get the original path value of a record by following its aliasOf
  2159. * @param record
  2160. */
  2161. function getOriginalPath(record) {
  2162. return record ? (record.aliasOf ? record.aliasOf.path : record.path) : '';
  2163. }
  2164. /**
  2165. * Utility class to get the active class based on defaults.
  2166. * @param propClass
  2167. * @param globalClass
  2168. * @param defaultClass
  2169. */
  2170. const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null
  2171. ? propClass
  2172. : globalClass != null
  2173. ? globalClass
  2174. : defaultClass;
  2175. const RouterViewImpl = /*#__PURE__*/ vue.defineComponent({
  2176. name: 'RouterView',
  2177. // #674 we manually inherit them
  2178. inheritAttrs: false,
  2179. props: {
  2180. name: {
  2181. type: String,
  2182. default: 'default',
  2183. },
  2184. route: Object,
  2185. },
  2186. // Better compat for @vue/compat users
  2187. // https://github.com/vuejs/router/issues/1315
  2188. compatConfig: { MODE: 3 },
  2189. setup(props, { attrs, slots }) {
  2190. const injectedRoute = vue.inject(routerViewLocationKey);
  2191. const routeToDisplay = vue.computed(() => props.route || injectedRoute.value);
  2192. const injectedDepth = vue.inject(viewDepthKey, 0);
  2193. // The depth changes based on empty components option, which allows passthrough routes e.g. routes with children
  2194. // that are used to reuse the `path` property
  2195. const depth = vue.computed(() => {
  2196. let initialDepth = vue.unref(injectedDepth);
  2197. const { matched } = routeToDisplay.value;
  2198. let matchedRoute;
  2199. while ((matchedRoute = matched[initialDepth]) &&
  2200. !matchedRoute.components) {
  2201. initialDepth++;
  2202. }
  2203. return initialDepth;
  2204. });
  2205. const matchedRouteRef = vue.computed(() => routeToDisplay.value.matched[depth.value]);
  2206. vue.provide(viewDepthKey, vue.computed(() => depth.value + 1));
  2207. vue.provide(matchedRouteKey, matchedRouteRef);
  2208. vue.provide(routerViewLocationKey, routeToDisplay);
  2209. const viewRef = vue.ref();
  2210. // watch at the same time the component instance, the route record we are
  2211. // rendering, and the name
  2212. vue.watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
  2213. // copy reused instances
  2214. if (to) {
  2215. // this will update the instance for new instances as well as reused
  2216. // instances when navigating to a new route
  2217. to.instances[name] = instance;
  2218. // the component instance is reused for a different route or name, so
  2219. // we copy any saved update or leave guards. With async setup, the
  2220. // mounting component will mount before the matchedRoute changes,
  2221. // making instance === oldInstance, so we check if guards have been
  2222. // added before. This works because we remove guards when
  2223. // unmounting/deactivating components
  2224. if (from && from !== to && instance && instance === oldInstance) {
  2225. if (!to.leaveGuards.size) {
  2226. to.leaveGuards = from.leaveGuards;
  2227. }
  2228. if (!to.updateGuards.size) {
  2229. to.updateGuards = from.updateGuards;
  2230. }
  2231. }
  2232. }
  2233. // trigger beforeRouteEnter next callbacks
  2234. if (instance &&
  2235. to &&
  2236. // if there is no instance but to and from are the same this might be
  2237. // the first visit
  2238. (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
  2239. (to.enterCallbacks[name] || []).forEach(callback => callback(instance));
  2240. }
  2241. }, { flush: 'post' });
  2242. return () => {
  2243. const route = routeToDisplay.value;
  2244. // we need the value at the time we render because when we unmount, we
  2245. // navigated to a different location so the value is different
  2246. const currentName = props.name;
  2247. const matchedRoute = matchedRouteRef.value;
  2248. const ViewComponent = matchedRoute && matchedRoute.components[currentName];
  2249. if (!ViewComponent) {
  2250. return normalizeSlot(slots.default, { Component: ViewComponent, route });
  2251. }
  2252. // props from route configuration
  2253. const routePropsOption = matchedRoute.props[currentName];
  2254. const routeProps = routePropsOption
  2255. ? routePropsOption === true
  2256. ? route.params
  2257. : typeof routePropsOption === 'function'
  2258. ? routePropsOption(route)
  2259. : routePropsOption
  2260. : null;
  2261. const onVnodeUnmounted = vnode => {
  2262. // remove the instance reference to prevent leak
  2263. if (vnode.component.isUnmounted) {
  2264. matchedRoute.instances[currentName] = null;
  2265. }
  2266. };
  2267. const component = vue.h(ViewComponent, assign({}, routeProps, attrs, {
  2268. onVnodeUnmounted,
  2269. ref: viewRef,
  2270. }));
  2271. return (
  2272. // pass the vnode to the slot as a prop.
  2273. // h and <component :is="..."> both accept vnodes
  2274. normalizeSlot(slots.default, { Component: component, route }) ||
  2275. component);
  2276. };
  2277. },
  2278. });
  2279. function normalizeSlot(slot, data) {
  2280. if (!slot)
  2281. return null;
  2282. const slotContent = slot(data);
  2283. return slotContent.length === 1 ? slotContent[0] : slotContent;
  2284. }
  2285. // export the public type for h/tsx inference
  2286. // also to avoid inline import() in generated d.ts files
  2287. /**
  2288. * Component to display the current route the user is at.
  2289. */
  2290. const RouterView = RouterViewImpl;
  2291. /**
  2292. * Creates a Router instance that can be used by a Vue app.
  2293. *
  2294. * @param options - {@link RouterOptions}
  2295. */
  2296. function createRouter(options) {
  2297. const matcher = createRouterMatcher(options.routes, options);
  2298. const parseQuery$1 = options.parseQuery || parseQuery;
  2299. const stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
  2300. const routerHistory = options.history;
  2301. const beforeGuards = useCallbacks();
  2302. const beforeResolveGuards = useCallbacks();
  2303. const afterGuards = useCallbacks();
  2304. const currentRoute = vue.shallowRef(START_LOCATION_NORMALIZED);
  2305. let pendingLocation = START_LOCATION_NORMALIZED;
  2306. // leave the scrollRestoration if no scrollBehavior is provided
  2307. if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {
  2308. history.scrollRestoration = 'manual';
  2309. }
  2310. const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue);
  2311. const encodeParams = applyToParams.bind(null, encodeParam);
  2312. const decodeParams =
  2313. // @ts-expect-error: intentionally avoid the type check
  2314. applyToParams.bind(null, decode);
  2315. function addRoute(parentOrRoute, route) {
  2316. let parent;
  2317. let record;
  2318. if (isRouteName(parentOrRoute)) {
  2319. parent = matcher.getRecordMatcher(parentOrRoute);
  2320. record = route;
  2321. }
  2322. else {
  2323. record = parentOrRoute;
  2324. }
  2325. return matcher.addRoute(record, parent);
  2326. }
  2327. function removeRoute(name) {
  2328. const recordMatcher = matcher.getRecordMatcher(name);
  2329. if (recordMatcher) {
  2330. matcher.removeRoute(recordMatcher);
  2331. }
  2332. }
  2333. function getRoutes() {
  2334. return matcher.getRoutes().map(routeMatcher => routeMatcher.record);
  2335. }
  2336. function hasRoute(name) {
  2337. return !!matcher.getRecordMatcher(name);
  2338. }
  2339. function resolve(rawLocation, currentLocation) {
  2340. // const resolve: Router['resolve'] = (rawLocation: RouteLocationRaw, currentLocation) => {
  2341. // const objectLocation = routerLocationAsObject(rawLocation)
  2342. // we create a copy to modify it later
  2343. currentLocation = assign({}, currentLocation || currentRoute.value);
  2344. if (typeof rawLocation === 'string') {
  2345. const locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
  2346. const matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
  2347. const href = routerHistory.createHref(locationNormalized.fullPath);
  2348. // locationNormalized is always a new object
  2349. return assign(locationNormalized, matchedRoute, {
  2350. params: decodeParams(matchedRoute.params),
  2351. hash: decode(locationNormalized.hash),
  2352. redirectedFrom: undefined,
  2353. href,
  2354. });
  2355. }
  2356. let matcherLocation;
  2357. // path could be relative in object as well
  2358. if (rawLocation.path != null) {
  2359. matcherLocation = assign({}, rawLocation, {
  2360. path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
  2361. });
  2362. }
  2363. else {
  2364. // remove any nullish param
  2365. const targetParams = assign({}, rawLocation.params);
  2366. for (const key in targetParams) {
  2367. if (targetParams[key] == null) {
  2368. delete targetParams[key];
  2369. }
  2370. }
  2371. // pass encoded values to the matcher, so it can produce encoded path and fullPath
  2372. matcherLocation = assign({}, rawLocation, {
  2373. params: encodeParams(targetParams),
  2374. });
  2375. // current location params are decoded, we need to encode them in case the
  2376. // matcher merges the params
  2377. currentLocation.params = encodeParams(currentLocation.params);
  2378. }
  2379. const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
  2380. const hash = rawLocation.hash || '';
  2381. // the matcher might have merged current location params, so
  2382. // we need to run the decoding again
  2383. matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
  2384. const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
  2385. hash: encodeHash(hash),
  2386. path: matchedRoute.path,
  2387. }));
  2388. const href = routerHistory.createHref(fullPath);
  2389. return assign({
  2390. fullPath,
  2391. // keep the hash encoded so fullPath is effectively path + encodedQuery +
  2392. // hash
  2393. hash,
  2394. query:
  2395. // if the user is using a custom query lib like qs, we might have
  2396. // nested objects, so we keep the query as is, meaning it can contain
  2397. // numbers at `$route.query`, but at the point, the user will have to
  2398. // use their own type anyway.
  2399. // https://github.com/vuejs/router/issues/328#issuecomment-649481567
  2400. stringifyQuery$1 === stringifyQuery
  2401. ? normalizeQuery(rawLocation.query)
  2402. : (rawLocation.query || {}),
  2403. }, matchedRoute, {
  2404. redirectedFrom: undefined,
  2405. href,
  2406. });
  2407. }
  2408. function locationAsObject(to) {
  2409. return typeof to === 'string'
  2410. ? parseURL(parseQuery$1, to, currentRoute.value.path)
  2411. : assign({}, to);
  2412. }
  2413. function checkCanceledNavigation(to, from) {
  2414. if (pendingLocation !== to) {
  2415. return createRouterError(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
  2416. from,
  2417. to,
  2418. });
  2419. }
  2420. }
  2421. function push(to) {
  2422. return pushWithRedirect(to);
  2423. }
  2424. function replace(to) {
  2425. return push(assign(locationAsObject(to), { replace: true }));
  2426. }
  2427. function handleRedirectRecord(to) {
  2428. const lastMatched = to.matched[to.matched.length - 1];
  2429. if (lastMatched && lastMatched.redirect) {
  2430. const { redirect } = lastMatched;
  2431. let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect;
  2432. if (typeof newTargetLocation === 'string') {
  2433. newTargetLocation =
  2434. newTargetLocation.includes('?') || newTargetLocation.includes('#')
  2435. ? (newTargetLocation = locationAsObject(newTargetLocation))
  2436. : // force empty params
  2437. { path: newTargetLocation };
  2438. // @ts-expect-error: force empty params when a string is passed to let
  2439. // the router parse them again
  2440. newTargetLocation.params = {};
  2441. }
  2442. return assign({
  2443. query: to.query,
  2444. hash: to.hash,
  2445. // avoid transferring params if the redirect has a path
  2446. params: newTargetLocation.path != null ? {} : to.params,
  2447. }, newTargetLocation);
  2448. }
  2449. }
  2450. function pushWithRedirect(to, redirectedFrom) {
  2451. const targetLocation = (pendingLocation = resolve(to));
  2452. const from = currentRoute.value;
  2453. const data = to.state;
  2454. const force = to.force;
  2455. // to could be a string where `replace` is a function
  2456. const replace = to.replace === true;
  2457. const shouldRedirect = handleRedirectRecord(targetLocation);
  2458. if (shouldRedirect)
  2459. return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
  2460. state: typeof shouldRedirect === 'object'
  2461. ? assign({}, data, shouldRedirect.state)
  2462. : data,
  2463. force,
  2464. replace,
  2465. }),
  2466. // keep original redirectedFrom if it exists
  2467. redirectedFrom || targetLocation);
  2468. // if it was a redirect we already called `pushWithRedirect` above
  2469. const toLocation = targetLocation;
  2470. toLocation.redirectedFrom = redirectedFrom;
  2471. let failure;
  2472. if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
  2473. failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
  2474. // trigger scroll to allow scrolling to the same anchor
  2475. handleScroll(from, from,
  2476. // this is a push, the only way for it to be triggered from a
  2477. // history.listen is with a redirect, which makes it become a push
  2478. true,
  2479. // This cannot be the first navigation because the initial location
  2480. // cannot be manually navigated to
  2481. false);
  2482. }
  2483. return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
  2484. .catch((error) => isNavigationFailure(error)
  2485. ? // navigation redirects still mark the router as ready
  2486. isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
  2487. ? error
  2488. : markAsReady(error) // also returns the error
  2489. : // reject any unknown error
  2490. triggerError(error, toLocation, from))
  2491. .then((failure) => {
  2492. if (failure) {
  2493. if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
  2494. return pushWithRedirect(
  2495. // keep options
  2496. assign({
  2497. // preserve an existing replacement but allow the redirect to override it
  2498. replace,
  2499. }, locationAsObject(failure.to), {
  2500. state: typeof failure.to === 'object'
  2501. ? assign({}, data, failure.to.state)
  2502. : data,
  2503. force,
  2504. }),
  2505. // preserve the original redirectedFrom if any
  2506. redirectedFrom || toLocation);
  2507. }
  2508. }
  2509. else {
  2510. // if we fail we don't finalize the navigation
  2511. failure = finalizeNavigation(toLocation, from, true, replace, data);
  2512. }
  2513. triggerAfterEach(toLocation, from, failure);
  2514. return failure;
  2515. });
  2516. }
  2517. /**
  2518. * Helper to reject and skip all navigation guards if a new navigation happened
  2519. * @param to
  2520. * @param from
  2521. */
  2522. function checkCanceledNavigationAndReject(to, from) {
  2523. const error = checkCanceledNavigation(to, from);
  2524. return error ? Promise.reject(error) : Promise.resolve();
  2525. }
  2526. function runWithContext(fn) {
  2527. const app = installedApps.values().next().value;
  2528. // support Vue < 3.3
  2529. return app && typeof app.runWithContext === 'function'
  2530. ? app.runWithContext(fn)
  2531. : fn();
  2532. }
  2533. // TODO: refactor the whole before guards by internally using router.beforeEach
  2534. function navigate(to, from) {
  2535. let guards;
  2536. const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);
  2537. // all components here have been resolved once because we are leaving
  2538. guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);
  2539. // leavingRecords is already reversed
  2540. for (const record of leavingRecords) {
  2541. record.leaveGuards.forEach(guard => {
  2542. guards.push(guardToPromiseFn(guard, to, from));
  2543. });
  2544. }
  2545. const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
  2546. guards.push(canceledNavigationCheck);
  2547. // run the queue of per route beforeRouteLeave guards
  2548. return (runGuardQueue(guards)
  2549. .then(() => {
  2550. // check global guards beforeEach
  2551. guards = [];
  2552. for (const guard of beforeGuards.list()) {
  2553. guards.push(guardToPromiseFn(guard, to, from));
  2554. }
  2555. guards.push(canceledNavigationCheck);
  2556. return runGuardQueue(guards);
  2557. })
  2558. .then(() => {
  2559. // check in components beforeRouteUpdate
  2560. guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);
  2561. for (const record of updatingRecords) {
  2562. record.updateGuards.forEach(guard => {
  2563. guards.push(guardToPromiseFn(guard, to, from));
  2564. });
  2565. }
  2566. guards.push(canceledNavigationCheck);
  2567. // run the queue of per route beforeEnter guards
  2568. return runGuardQueue(guards);
  2569. })
  2570. .then(() => {
  2571. // check the route beforeEnter
  2572. guards = [];
  2573. for (const record of enteringRecords) {
  2574. // do not trigger beforeEnter on reused views
  2575. if (record.beforeEnter) {
  2576. if (isArray(record.beforeEnter)) {
  2577. for (const beforeEnter of record.beforeEnter)
  2578. guards.push(guardToPromiseFn(beforeEnter, to, from));
  2579. }
  2580. else {
  2581. guards.push(guardToPromiseFn(record.beforeEnter, to, from));
  2582. }
  2583. }
  2584. }
  2585. guards.push(canceledNavigationCheck);
  2586. // run the queue of per route beforeEnter guards
  2587. return runGuardQueue(guards);
  2588. })
  2589. .then(() => {
  2590. // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
  2591. // clear existing enterCallbacks, these are added by extractComponentsGuards
  2592. to.matched.forEach(record => (record.enterCallbacks = {}));
  2593. // check in-component beforeRouteEnter
  2594. guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from, runWithContext);
  2595. guards.push(canceledNavigationCheck);
  2596. // run the queue of per route beforeEnter guards
  2597. return runGuardQueue(guards);
  2598. })
  2599. .then(() => {
  2600. // check global guards beforeResolve
  2601. guards = [];
  2602. for (const guard of beforeResolveGuards.list()) {
  2603. guards.push(guardToPromiseFn(guard, to, from));
  2604. }
  2605. guards.push(canceledNavigationCheck);
  2606. return runGuardQueue(guards);
  2607. })
  2608. // catch any navigation canceled
  2609. .catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
  2610. ? err
  2611. : Promise.reject(err)));
  2612. }
  2613. function triggerAfterEach(to, from, failure) {
  2614. // navigation is confirmed, call afterGuards
  2615. // TODO: wrap with error handlers
  2616. afterGuards
  2617. .list()
  2618. .forEach(guard => runWithContext(() => guard(to, from, failure)));
  2619. }
  2620. /**
  2621. * - Cleans up any navigation guards
  2622. * - Changes the url if necessary
  2623. * - Calls the scrollBehavior
  2624. */
  2625. function finalizeNavigation(toLocation, from, isPush, replace, data) {
  2626. // a more recent navigation took place
  2627. const error = checkCanceledNavigation(toLocation, from);
  2628. if (error)
  2629. return error;
  2630. // only consider as push if it's not the first navigation
  2631. const isFirstNavigation = from === START_LOCATION_NORMALIZED;
  2632. const state = !isBrowser ? {} : history.state;
  2633. // change URL only if the user did a push/replace and if it's not the initial navigation because
  2634. // it's just reflecting the url
  2635. if (isPush) {
  2636. // on the initial navigation, we want to reuse the scroll position from
  2637. // history state if it exists
  2638. if (replace || isFirstNavigation)
  2639. routerHistory.replace(toLocation.fullPath, assign({
  2640. scroll: isFirstNavigation && state && state.scroll,
  2641. }, data));
  2642. else
  2643. routerHistory.push(toLocation.fullPath, data);
  2644. }
  2645. // accept current navigation
  2646. currentRoute.value = toLocation;
  2647. handleScroll(toLocation, from, isPush, isFirstNavigation);
  2648. markAsReady();
  2649. }
  2650. let removeHistoryListener;
  2651. // attach listener to history to trigger navigations
  2652. function setupListeners() {
  2653. // avoid setting up listeners twice due to an invalid first navigation
  2654. if (removeHistoryListener)
  2655. return;
  2656. removeHistoryListener = routerHistory.listen((to, _from, info) => {
  2657. if (!router.listening)
  2658. return;
  2659. // cannot be a redirect route because it was in history
  2660. const toLocation = resolve(to);
  2661. // due to dynamic routing, and to hash history with manual navigation
  2662. // (manually changing the url or calling history.hash = '#/somewhere'),
  2663. // there could be a redirect record in history
  2664. const shouldRedirect = handleRedirectRecord(toLocation);
  2665. if (shouldRedirect) {
  2666. pushWithRedirect(assign(shouldRedirect, { replace: true, force: true }), toLocation).catch(noop);
  2667. return;
  2668. }
  2669. pendingLocation = toLocation;
  2670. const from = currentRoute.value;
  2671. // TODO: should be moved to web history?
  2672. if (isBrowser) {
  2673. saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
  2674. }
  2675. navigate(toLocation, from)
  2676. .catch((error) => {
  2677. if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
  2678. return error;
  2679. }
  2680. if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
  2681. // Here we could call if (info.delta) routerHistory.go(-info.delta,
  2682. // false) but this is bug prone as we have no way to wait the
  2683. // navigation to be finished before calling pushWithRedirect. Using
  2684. // a setTimeout of 16ms seems to work but there is no guarantee for
  2685. // it to work on every browser. So instead we do not restore the
  2686. // history entry and trigger a new navigation as requested by the
  2687. // navigation guard.
  2688. // the error is already handled by router.push we just want to avoid
  2689. // logging the error
  2690. pushWithRedirect(assign(locationAsObject(error.to), {
  2691. force: true,
  2692. }), toLocation
  2693. // avoid an uncaught rejection, let push call triggerError
  2694. )
  2695. .then(failure => {
  2696. // manual change in hash history #916 ending up in the URL not
  2697. // changing, but it was changed by the manual url change, so we
  2698. // need to manually change it ourselves
  2699. if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
  2700. 16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
  2701. !info.delta &&
  2702. info.type === NavigationType.pop) {
  2703. routerHistory.go(-1, false);
  2704. }
  2705. })
  2706. .catch(noop);
  2707. // avoid the then branch
  2708. return Promise.reject();
  2709. }
  2710. // do not restore history on unknown direction
  2711. if (info.delta) {
  2712. routerHistory.go(-info.delta, false);
  2713. }
  2714. // unrecognized error, transfer to the global handler
  2715. return triggerError(error, toLocation, from);
  2716. })
  2717. .then((failure) => {
  2718. failure =
  2719. failure ||
  2720. finalizeNavigation(
  2721. // after navigation, all matched components are resolved
  2722. toLocation, from, false);
  2723. // revert the navigation
  2724. if (failure) {
  2725. if (info.delta &&
  2726. // a new navigation has been triggered, so we do not want to revert, that will change the current history
  2727. // entry while a different route is displayed
  2728. !isNavigationFailure(failure, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
  2729. routerHistory.go(-info.delta, false);
  2730. }
  2731. else if (info.type === NavigationType.pop &&
  2732. isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
  2733. // manual change in hash history #916
  2734. // it's like a push but lacks the information of the direction
  2735. routerHistory.go(-1, false);
  2736. }
  2737. }
  2738. triggerAfterEach(toLocation, from, failure);
  2739. })
  2740. // avoid warnings in the console about uncaught rejections, they are logged by triggerErrors
  2741. .catch(noop);
  2742. });
  2743. }
  2744. // Initialization and Errors
  2745. let readyHandlers = useCallbacks();
  2746. let errorListeners = useCallbacks();
  2747. let ready;
  2748. /**
  2749. * Trigger errorListeners added via onError and throws the error as well
  2750. *
  2751. * @param error - error to throw
  2752. * @param to - location we were navigating to when the error happened
  2753. * @param from - location we were navigating from when the error happened
  2754. * @returns the error as a rejected promise
  2755. */
  2756. function triggerError(error, to, from) {
  2757. markAsReady(error);
  2758. const list = errorListeners.list();
  2759. if (list.length) {
  2760. list.forEach(handler => handler(error, to, from));
  2761. }
  2762. else {
  2763. console.error(error);
  2764. }
  2765. // reject the error no matter there were error listeners or not
  2766. return Promise.reject(error);
  2767. }
  2768. function isReady() {
  2769. if (ready && currentRoute.value !== START_LOCATION_NORMALIZED)
  2770. return Promise.resolve();
  2771. return new Promise((resolve, reject) => {
  2772. readyHandlers.add([resolve, reject]);
  2773. });
  2774. }
  2775. function markAsReady(err) {
  2776. if (!ready) {
  2777. // still not ready if an error happened
  2778. ready = !err;
  2779. setupListeners();
  2780. readyHandlers
  2781. .list()
  2782. .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
  2783. readyHandlers.reset();
  2784. }
  2785. return err;
  2786. }
  2787. // Scroll behavior
  2788. function handleScroll(to, from, isPush, isFirstNavigation) {
  2789. const { scrollBehavior } = options;
  2790. if (!isBrowser || !scrollBehavior)
  2791. return Promise.resolve();
  2792. const scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||
  2793. ((isFirstNavigation || !isPush) &&
  2794. history.state &&
  2795. history.state.scroll) ||
  2796. null;
  2797. return vue.nextTick()
  2798. .then(() => scrollBehavior(to, from, scrollPosition))
  2799. .then(position => position && scrollToPosition(position))
  2800. .catch(err => triggerError(err, to, from));
  2801. }
  2802. const go = (delta) => routerHistory.go(delta);
  2803. let started;
  2804. const installedApps = new Set();
  2805. const router = {
  2806. currentRoute,
  2807. listening: true,
  2808. addRoute,
  2809. removeRoute,
  2810. clearRoutes: matcher.clearRoutes,
  2811. hasRoute,
  2812. getRoutes,
  2813. resolve,
  2814. options,
  2815. push,
  2816. replace,
  2817. go,
  2818. back: () => go(-1),
  2819. forward: () => go(1),
  2820. beforeEach: beforeGuards.add,
  2821. beforeResolve: beforeResolveGuards.add,
  2822. afterEach: afterGuards.add,
  2823. onError: errorListeners.add,
  2824. isReady,
  2825. install(app) {
  2826. const router = this;
  2827. app.component('RouterLink', RouterLink);
  2828. app.component('RouterView', RouterView);
  2829. app.config.globalProperties.$router = router;
  2830. Object.defineProperty(app.config.globalProperties, '$route', {
  2831. enumerable: true,
  2832. get: () => vue.unref(currentRoute),
  2833. });
  2834. // this initial navigation is only necessary on client, on server it doesn't
  2835. // make sense because it will create an extra unnecessary navigation and could
  2836. // lead to problems
  2837. if (isBrowser &&
  2838. // used for the initial navigation client side to avoid pushing
  2839. // multiple times when the router is used in multiple apps
  2840. !started &&
  2841. currentRoute.value === START_LOCATION_NORMALIZED) {
  2842. // see above
  2843. started = true;
  2844. push(routerHistory.location).catch(err => {
  2845. });
  2846. }
  2847. const reactiveRoute = {};
  2848. for (const key in START_LOCATION_NORMALIZED) {
  2849. Object.defineProperty(reactiveRoute, key, {
  2850. get: () => currentRoute.value[key],
  2851. enumerable: true,
  2852. });
  2853. }
  2854. app.provide(routerKey, router);
  2855. app.provide(routeLocationKey, vue.shallowReactive(reactiveRoute));
  2856. app.provide(routerViewLocationKey, currentRoute);
  2857. const unmountApp = app.unmount;
  2858. installedApps.add(app);
  2859. app.unmount = function () {
  2860. installedApps.delete(app);
  2861. // the router is not attached to an app anymore
  2862. if (installedApps.size < 1) {
  2863. // invalidate the current navigation
  2864. pendingLocation = START_LOCATION_NORMALIZED;
  2865. removeHistoryListener && removeHistoryListener();
  2866. removeHistoryListener = null;
  2867. currentRoute.value = START_LOCATION_NORMALIZED;
  2868. started = false;
  2869. ready = false;
  2870. }
  2871. unmountApp();
  2872. };
  2873. },
  2874. };
  2875. // TODO: type this as NavigationGuardReturn or similar instead of any
  2876. function runGuardQueue(guards) {
  2877. return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
  2878. }
  2879. return router;
  2880. }
  2881. function extractChangingRecords(to, from) {
  2882. const leavingRecords = [];
  2883. const updatingRecords = [];
  2884. const enteringRecords = [];
  2885. const len = Math.max(from.matched.length, to.matched.length);
  2886. for (let i = 0; i < len; i++) {
  2887. const recordFrom = from.matched[i];
  2888. if (recordFrom) {
  2889. if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))
  2890. updatingRecords.push(recordFrom);
  2891. else
  2892. leavingRecords.push(recordFrom);
  2893. }
  2894. const recordTo = to.matched[i];
  2895. if (recordTo) {
  2896. // the type doesn't matter because we are comparing per reference
  2897. if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
  2898. enteringRecords.push(recordTo);
  2899. }
  2900. }
  2901. }
  2902. return [leavingRecords, updatingRecords, enteringRecords];
  2903. }
  2904. /**
  2905. * Returns the router instance. Equivalent to using `$router` inside
  2906. * templates.
  2907. */
  2908. function useRouter() {
  2909. return vue.inject(routerKey);
  2910. }
  2911. /**
  2912. * Returns the current route location. Equivalent to using `$route` inside
  2913. * templates.
  2914. */
  2915. function useRoute(_name) {
  2916. return vue.inject(routeLocationKey);
  2917. }
  2918. exports.RouterLink = RouterLink;
  2919. exports.RouterView = RouterView;
  2920. exports.START_LOCATION = START_LOCATION_NORMALIZED;
  2921. exports.createMemoryHistory = createMemoryHistory;
  2922. exports.createRouter = createRouter;
  2923. exports.createRouterMatcher = createRouterMatcher;
  2924. exports.createWebHashHistory = createWebHashHistory;
  2925. exports.createWebHistory = createWebHistory;
  2926. exports.isNavigationFailure = isNavigationFailure;
  2927. exports.loadRouteLocation = loadRouteLocation;
  2928. exports.matchedRouteKey = matchedRouteKey;
  2929. exports.onBeforeRouteLeave = onBeforeRouteLeave;
  2930. exports.onBeforeRouteUpdate = onBeforeRouteUpdate;
  2931. exports.parseQuery = parseQuery;
  2932. exports.routeLocationKey = routeLocationKey;
  2933. exports.routerKey = routerKey;
  2934. exports.routerViewLocationKey = routerViewLocationKey;
  2935. exports.stringifyQuery = stringifyQuery;
  2936. exports.useLink = useLink;
  2937. exports.useRoute = useRoute;
  2938. exports.useRouter = useRouter;
  2939. exports.viewDepthKey = viewDepthKey;