From db39ae1e5c7064bd498ac58343ab4ac03d77ce9a Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 19 May 2020 13:29:35 +0200 Subject: [PATCH 01/15] chore(changelog): 3.2.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9843a0ad..4dd8df52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# [3.2.0](https://github.com/vuejs/vue-router/compare/v3.1.6...v3.2.0) (2020-05-19) + + +### Bug Fixes + +* **html5:** make base case insensitive ([04a2143](https://github.com/vuejs/vue-router/commit/04a2143)), closes [#2154](https://github.com/vuejs/vue-router/issues/2154) +* check for pushState being a function ([bc41f67](https://github.com/vuejs/vue-router/commit/bc41f67)), closes [#3154](https://github.com/vuejs/vue-router/issues/3154) + + +### Features + +* **link:** add aria-current to active links (close [#2116](https://github.com/vuejs/vue-router/issues/2116)) ([#3073](https://github.com/vuejs/vue-router/issues/3073)) ([6ec0ee5](https://github.com/vuejs/vue-router/commit/6ec0ee5)) +* **scroll:** use manual scrollRestoration with scrollBehavior ([#1814](https://github.com/vuejs/vue-router/issues/1814)) ([1261363](https://github.com/vuejs/vue-router/commit/1261363)) +* **types:** NavigationGuardNext ([#2497](https://github.com/vuejs/vue-router/issues/2497)) ([d18c497](https://github.com/vuejs/vue-router/commit/d18c497)) + + + ## [3.1.6](https://github.com/vuejs/vue-router/compare/v3.1.5...v3.1.6) (2020-02-26) From 4c81be8ffb00b545396766f0a7ffff3c779b64db Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Tue, 19 May 2020 05:49:51 -0600 Subject: [PATCH 02/15] feat(history): Remove event listeners when all apps are destroyed. (#3172) * feat(history): Remove event listeners when all apps are destroyed. * fix(tests): Adding test assertions * fix(feedback): addressing @posva's feedback * fix(index): posva's feedback * feat(tests): adding multi-app test * fix(feedback): posva's feedback * fix(feedback): unmounting apps with buttons outside of the app Close #3152 Close #2341 --- examples/basic/app.js | 31 ++++++++++++++- examples/basic/index.html | 2 + examples/hash-mode/app.js | 35 +++++++++++++++- examples/hash-mode/index.html | 2 + examples/index.html | 1 + examples/multi-app/app.js | 75 +++++++++++++++++++++++++++++++++++ examples/multi-app/index.html | 24 +++++++++++ src/history/base.js | 15 +++++++ src/history/hash.js | 41 +++++++++++-------- src/history/html5.js | 22 ++++++++-- src/index.js | 18 ++++----- src/util/scroll.js | 17 +++++--- test/e2e/specs/basic.js | 5 +++ test/e2e/specs/hash-mode.js | 6 +++ test/e2e/specs/multi-app.js | 49 +++++++++++++++++++++++ 15 files changed, 305 insertions(+), 38 deletions(-) create mode 100644 examples/multi-app/app.js create mode 100644 examples/multi-app/index.html create mode 100644 test/e2e/specs/multi-app.js diff --git a/examples/basic/app.js b/examples/basic/app.js index 6033475de..ba13679f2 100644 --- a/examples/basic/app.js +++ b/examples/basic/app.js @@ -1,6 +1,30 @@ import Vue from 'vue' import VueRouter from 'vue-router' +// track number of popstate listeners +let numPopstateListeners = 0 +const listenerCountDiv = document.createElement('div') +listenerCountDiv.id = 'popstate-count' +listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' +document.body.appendChild(listenerCountDiv) + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + ' popstate listeners' + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + ' popstate listeners' + } + return originalRemoveEventListener.apply(this, arguments) +} + // 1. Use plugin. // This installs and , // and injects $router and $route to all router-enabled child components @@ -27,7 +51,7 @@ const router = new VueRouter({ // 4. Create and mount root instance. // Make sure to inject the router. // Route components will be rendered inside . -new Vue({ +const vueInstance = new Vue({ router, data: () => ({ n: 0 }), template: ` @@ -69,3 +93,8 @@ new Vue({ } } }).$mount('#app') + +document.getElementById('unmount').addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' +}) diff --git a/examples/basic/index.html b/examples/basic/index.html index 78a0c040f..695d668f5 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -1,6 +1,8 @@ ← Examples index + +
diff --git a/examples/hash-mode/app.js b/examples/hash-mode/app.js index c8081cf2a..48b9ab1d6 100644 --- a/examples/hash-mode/app.js +++ b/examples/hash-mode/app.js @@ -1,6 +1,30 @@ import Vue from 'vue' import VueRouter from 'vue-router' +// track number of popstate listeners +let numPopstateListeners = 0 +const listenerCountDiv = document.createElement('div') +listenerCountDiv.id = 'popstate-count' +listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' +document.body.appendChild(listenerCountDiv) + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + ' popstate listeners' + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + ' popstate listeners' + } + return originalRemoveEventListener.apply(this, arguments) +} + // 1. Use plugin. // This installs and , // and injects $router and $route to all router-enabled child components @@ -28,7 +52,7 @@ const router = new VueRouter({ // 4. Create and mount root instance. // Make sure to inject the router. // Route components will be rendered inside . -new Vue({ +const vueInstance = new Vue({ router, template: `
@@ -47,5 +71,12 @@ new Vue({
{{ $route.hash }}
- ` + `, + methods: { + } }).$mount('#app') + +document.getElementById('unmount').addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' +}) diff --git a/examples/hash-mode/index.html b/examples/hash-mode/index.html index 68e93063a..5789d784f 100644 --- a/examples/hash-mode/index.html +++ b/examples/hash-mode/index.html @@ -1,6 +1,8 @@ ← Examples index + +
diff --git a/examples/index.html b/examples/index.html index c0ee5bc16..864beeb3b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -27,6 +27,7 @@

Vue Router Examples

  • Discrete Components
  • Nested Routers
  • Keepalive View
  • +
  • Multiple Apps
  • diff --git a/examples/multi-app/app.js b/examples/multi-app/app.js new file mode 100644 index 000000000..c9d28a0c7 --- /dev/null +++ b/examples/multi-app/app.js @@ -0,0 +1,75 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +// track number of popstate listeners +let numPopstateListeners = 0 +const listenerCountDiv = document.getElementById('popcount') +listenerCountDiv.textContent = 0 + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + } + return originalRemoveEventListener.apply(this, arguments) +} + +// 1. Use plugin. +// This installs and , +// and injects $router and $route to all router-enabled child components +Vue.use(VueRouter) + +const looper = [1, 2, 3] + +looper.forEach((n) => { + let vueInstance + const mountEl = document.getElementById('mount' + n) + const unmountEl = document.getElementById('unmount' + n) + + mountEl.addEventListener('click', () => { + // 2. Define route components + const Home = { template: '
    home
    ' } + const Foo = { template: '
    foo
    ' } + + // 3. Create the router + const router = new VueRouter({ + mode: 'history', + base: __dirname, + routes: [ + { path: '/', component: Home }, + { path: '/foo', component: Foo } + ] + }) + + // 4. Create and mount root instance. + // Make sure to inject the router. + // Route components will be rendered inside . + vueInstance = new Vue({ + router, + template: ` +
    +

    Basic

    +
      +
    • /
    • +
    • /foo
    • +
    + +
    + ` + }).$mount('#app-' + n) + }) + + unmountEl.addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' + }) +}) diff --git a/examples/multi-app/index.html b/examples/multi-app/index.html new file mode 100644 index 000000000..96f094114 --- /dev/null +++ b/examples/multi-app/index.html @@ -0,0 +1,24 @@ + + +← Examples index + + + + + +
    + + + + + +
    + +popstate count: + +
    +
    +
    + + + \ No newline at end of file diff --git a/src/history/base.js b/src/history/base.js index 2b12e1967..7680edb6d 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -23,6 +23,8 @@ export class History { readyCbs: Array readyErrorCbs: Array errorCbs: Array + listeners: Array + cleanupListeners: Function // implemented by sub-classes +go: (n: number) => void @@ -30,6 +32,7 @@ export class History { +replace: (loc: RawLocation) => void +ensureURL: (push?: boolean) => void +getCurrentLocation: () => string + +setupListeners: Function constructor (router: Router, base: ?string) { this.router = router @@ -41,6 +44,7 @@ export class History { this.readyCbs = [] this.readyErrorCbs = [] this.errorCbs = [] + this.listeners = [] } listen (cb: Function) { @@ -208,6 +212,17 @@ export class History { hook && hook(route, prev) }) } + + setupListeners () { + // Default implementation is empty + } + + teardownListeners () { + this.listeners.forEach(cleanupListener => { + cleanupListener() + }) + this.listeners = [] + } } function normalizeBase (base: ?string): string { diff --git a/src/history/hash.js b/src/history/hash.js index 62b9a3472..b3372e10d 100644 --- a/src/history/hash.js +++ b/src/history/hash.js @@ -20,31 +20,40 @@ export class HashHistory extends History { // this is delayed until the app mounts // to avoid the hashchange listener being fired too early setupListeners () { + if (this.listeners.length > 0) { + return + } + const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { - setupScroll() + this.listeners.push(setupScroll()) } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - () => { - const current = this.current - if (!ensureSlash()) { - return - } - this.transitionTo(getHash(), route => { - if (supportsScroll) { - handleScroll(this.router, route, current, true) - } - if (!supportsPushState) { - replaceHash(route.fullPath) - } - }) + const handleRoutingEvent = () => { + const current = this.current + if (!ensureSlash()) { + return } + this.transitionTo(getHash(), route => { + if (supportsScroll) { + handleScroll(this.router, route, current, true) + } + if (!supportsPushState) { + replaceHash(route.fullPath) + } + }) + } + const eventType = supportsPushState ? 'popstate' : 'hashchange' + window.addEventListener( + eventType, + handleRoutingEvent ) + this.listeners.push(() => { + window.removeEventListener(eventType, handleRoutingEvent) + }) } push (location: RawLocation, onComplete?: Function, onAbort?: Function) { diff --git a/src/history/html5.js b/src/history/html5.js index d4a47632e..8200b3b28 100644 --- a/src/history/html5.js +++ b/src/history/html5.js @@ -8,24 +8,34 @@ import { setupScroll, handleScroll } from '../util/scroll' import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HTML5History extends History { + _startLocation: string + constructor (router: Router, base: ?string) { super(router, base) + this._startLocation = getLocation(this.base) + } + + setupListeners () { + if (this.listeners.length > 0) { + return + } + + const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { - setupScroll() + this.listeners.push(setupScroll()) } - const initLocation = getLocation(this.base) - window.addEventListener('popstate', e => { + const handleRoutingEvent = () => { const current = this.current // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base) - if (this.current === START && location === initLocation) { + if (this.current === START && location === this._startLocation) { return } @@ -34,6 +44,10 @@ export class HTML5History extends History { handleScroll(router, route, current, true) } }) + } + window.addEventListener('popstate', handleRoutingEvent) + this.listeners.push(() => { + window.removeEventListener('popstate', handleRoutingEvent) }) } diff --git a/src/index.js b/src/index.js index e95ace6d1..d0fb34b7b 100644 --- a/src/index.js +++ b/src/index.js @@ -98,6 +98,12 @@ export default class VueRouter { // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this.app === app) this.app = this.apps[0] || null + + if (!this.app) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this.history.teardownListeners() + } }) // main app previously initialized @@ -110,17 +116,11 @@ export default class VueRouter { const history = this.history - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()) - } else if (history instanceof HashHistory) { - const setupHashListener = () => { + if (history instanceof HTML5History || history instanceof HashHistory) { + const setupListeners = () => { history.setupListeners() } - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ) + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners) } history.listen(route => { diff --git a/src/util/scroll.js b/src/util/scroll.js index d2588fec4..d12461e99 100644 --- a/src/util/scroll.js +++ b/src/util/scroll.js @@ -23,12 +23,10 @@ export function setupScroll () { const stateCopy = extend({}, window.history.state) stateCopy.key = getStateKey() window.history.replaceState(stateCopy, '', absolutePath) - window.addEventListener('popstate', e => { - saveScrollPosition() - if (e.state && e.state.key) { - setStateKey(e.state.key) - } - }) + window.addEventListener('popstate', handlePopState) + return () => { + window.removeEventListener('popstate', handlePopState) + } } export function handleScroll ( @@ -90,6 +88,13 @@ export function saveScrollPosition () { } } +function handlePopState (e) { + saveScrollPosition() + if (e.state && e.state.key) { + setStateKey(e.state.key) + } +} + function getScrollPosition (): ?Object { const key = getStateKey() if (key) { diff --git a/test/e2e/specs/basic.js b/test/e2e/specs/basic.js index f5e6364eb..f4d7d6f51 100644 --- a/test/e2e/specs/basic.js +++ b/test/e2e/specs/basic.js @@ -70,6 +70,11 @@ module.exports = { .assert.cssClassPresent('li:nth-child(8)', 'exact-active') .assert.attributeEquals('li:nth-child(8) a', 'class', '') + // Listener cleanup + .assert.containsText('#popstate-count', '1 popstate listeners') + .click('#unmount') + .assert.containsText('#popstate-count', '0 popstate listeners') + .end() } } diff --git a/test/e2e/specs/hash-mode.js b/test/e2e/specs/hash-mode.js index be5df7d14..569285a23 100644 --- a/test/e2e/specs/hash-mode.js +++ b/test/e2e/specs/hash-mode.js @@ -57,6 +57,12 @@ module.exports = { .waitForElementVisible('#app', 1000) .assert.containsText('.view', 'unicode: ñ') .assert.containsText('#query-t', '%') + + // Listener cleanup + .assert.containsText('#popstate-count', '1 popstate listeners') + .click('#unmount') + .assert.containsText('#popstate-count', '0 popstate listeners') + .end() } } diff --git a/test/e2e/specs/multi-app.js b/test/e2e/specs/multi-app.js new file mode 100644 index 000000000..a6b0a2af1 --- /dev/null +++ b/test/e2e/specs/multi-app.js @@ -0,0 +1,49 @@ +const bsStatus = require('../browserstack-send-status') + +module.exports = { + ...bsStatus(), + + '@tags': ['history'], + + basic: function (browser) { + browser + .url('http://localhost:8080/multi-app/') + .waitForElementVisible('#mount1', 1000) + .assert.containsText('#popcount', '0') + .click('#mount1') + .waitForElementVisible('#app-1 > *', 1000) + .assert.containsText('#popcount', '1') + .click('#mount2') + .waitForElementVisible('#app-2 > *', 1000) + .assert.containsText('#popcount', '2') + .click('#mount3') + .waitForElementVisible('#app-3 > *', 1000) + .assert.containsText('#popcount', '3') + + // They should all be displaying the home page + .assert.containsText('#app-1', 'home') + .assert.containsText('#app-2', 'home') + .assert.containsText('#app-3', 'home') + + // Navigate to foo route + .click('#app-1 li:nth-child(2) a') + .assert.containsText('#app-1', 'foo') + + .click('#app-2 li:nth-child(2) a') + .assert.containsText('#app-2', 'foo') + + .click('#app-3 li:nth-child(2) a') + .assert.containsText('#app-3', 'foo') + + // Unmount all apps + .assert.containsText('#popcount', '3') + .click('#unmount1') + .assert.containsText('#popcount', '2') + .click('#unmount2') + .assert.containsText('#popcount', '1') + .click('#unmount3') + .assert.containsText('#popcount', '0') + + .end() + } +} From 4c727f91f15b915df2864eaaf5fed63f0284e6b1 Mon Sep 17 00:00:00 2001 From: Louis-Marie Michelin <33673240+lmichelin@users.noreply.github.com> Date: Tue, 19 May 2020 14:08:28 +0200 Subject: [PATCH 03/15] feat(errors): create router errors (#3047) * test: add example for navigation errors * test: add unit tests for navigation errors * feat(errors): create router errors --- examples/index.html | 1 + examples/router-errors/app.js | 53 ++++++++++++++++ examples/router-errors/index.html | 8 +++ src/history/abstract.js | 6 +- src/history/base.js | 23 ++++--- src/history/errors.js | 85 ++++++++++++++++++++------ src/util/warn.js | 8 +-- test/unit/specs/error-handling.spec.js | 51 ++++++++++++++++ 8 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 examples/router-errors/app.js create mode 100644 examples/router-errors/index.html diff --git a/examples/index.html b/examples/index.html index 864beeb3b..f9fbf1b9a 100644 --- a/examples/index.html +++ b/examples/index.html @@ -18,6 +18,7 @@

    Vue Router Examples

  • Route Props
  • Route Alias
  • Route Params
  • +
  • Router errors
  • Transitions
  • Data Fetching
  • Navigation Guards
  • diff --git a/examples/router-errors/app.js b/examples/router-errors/app.js new file mode 100644 index 000000000..749d36463 --- /dev/null +++ b/examples/router-errors/app.js @@ -0,0 +1,53 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const component = { + template: ` +
    + {{ $route.fullPath }} +
    + ` +} + +Vue.use(VueRouter) + +const router = new VueRouter({ + routes: [ + { path: '/', component }, { path: '/foo', component } + ] +}) + +router.beforeEach((to, from, next) => { + console.log('from', from.fullPath) + console.log('going to', to.fullPath) + if (to.query.wait) { + setTimeout(() => next(), 100) + } else if (to.query.redirect) { + next(to.query.redirect) + } else if (to.query.abort) { + next(false) + } else { + next() + } +}) + +new Vue({ + el: '#app', + router +}) + +// 4 NAVIGATION ERROR CASES : + +// navigation duplicated +// router.push('/foo') +// router.push('/foo') + +// navigation cancelled +// router.push('/foo?wait=y') +// router.push('/') + +// navigation redirected +// router.push('/foo?redirect=/') + +// navigation aborted +// router.push('/foo?abort=y') diff --git a/examples/router-errors/index.html b/examples/router-errors/index.html new file mode 100644 index 000000000..12011560b --- /dev/null +++ b/examples/router-errors/index.html @@ -0,0 +1,8 @@ + +
    + / + /foo + +
    + + diff --git a/src/history/abstract.js b/src/history/abstract.js index 7b6bdf2a8..9437182e7 100644 --- a/src/history/abstract.js +++ b/src/history/abstract.js @@ -2,8 +2,8 @@ import type Router from '../index' import { History } from './base' -import { NavigationDuplicated } from './errors' -import { isExtendedError } from '../util/warn' +import { isRouterError } from '../util/warn' +import { NavigationFailureType } from './errors' export class AbstractHistory extends History { index: number @@ -51,7 +51,7 @@ export class AbstractHistory extends History { this.updateRoute(route) }, err => { - if (isExtendedError(NavigationDuplicated, err)) { + if (isRouterError(err, NavigationFailureType.NAVIGATION_DUPLICATED)) { this.index = targetIndex } } diff --git a/src/history/base.js b/src/history/base.js index 7680edb6d..2b9d71cae 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -4,14 +4,20 @@ import { _Vue } from '../install' import type Router from '../index' import { inBrowser } from '../util/dom' import { runQueue } from '../util/async' -import { warn, isError, isExtendedError } from '../util/warn' +import { warn, isError, isRouterError } from '../util/warn' import { START, isSameRoute } from '../util/route' import { flatten, flatMapComponents, resolveAsyncComponents } from '../util/resolve-components' -import { NavigationDuplicated } from './errors' +import { + createNavigationDuplicatedError, + createNavigationCancelledError, + createNavigationRedirectedError, + createNavigationAbortedError, + NavigationFailureType +} from './errors' export class History { router: Router @@ -108,7 +114,7 @@ export class History { // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.NAVIGATION_DUPLICATED) && isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err) @@ -126,7 +132,7 @@ export class History { route.matched.length === current.matched.length ) { this.ensureURL() - return abort(new NavigationDuplicated(route)) + return abort(createNavigationDuplicatedError(current, route)) } const { updated, deactivated, activated } = resolveQueue( @@ -150,12 +156,15 @@ export class History { this.pending = route const iterator = (hook: NavigationGuard, next) => { if (this.pending !== route) { - return abort() + return abort(createNavigationCancelledError(current, route)) } try { hook(route, current, (to: any) => { - if (to === false || isError(to)) { + if (to === false) { // next(false) -> abort navigation, ensure current URL + this.ensureURL(true) + abort(createNavigationAbortedError(current, route)) + } else if (isError(to)) { this.ensureURL(true) abort(to) } else if ( @@ -164,7 +173,7 @@ export class History { (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect - abort() + abort(createNavigationRedirectedError(current, route)) if (typeof to === 'object' && to.replace) { this.replace(to) } else { diff --git a/src/history/errors.js b/src/history/errors.js index 1d2118c57..4fac5725a 100644 --- a/src/history/errors.js +++ b/src/history/errors.js @@ -1,22 +1,67 @@ -export class NavigationDuplicated extends Error { - constructor (normalizedLocation) { - super() - this.name = this._name = 'NavigationDuplicated' - // passing the message to super() doesn't seem to work in the transpiled version - this.message = `Navigating to current location ("${ - normalizedLocation.fullPath - }") is not allowed` - // add a stack property so services like Sentry can correctly display it - Object.defineProperty(this, 'stack', { - value: new Error().stack, - writable: true, - configurable: true - }) - // we could also have used - // Error.captureStackTrace(this, this.constructor) - // but it only exists on node and chrome - } +export const NavigationFailureType = { + redirected: 1, + aborted: 2, + cancelled: 3, + duplicated: 4 +} + +export function createNavigationRedirectedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.redirected, + `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.` + ) +} + +export function createNavigationDuplicatedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.duplicated, + `Avoided redundant navigation to current location: "${from.fullPath}".` + ) +} + +export function createNavigationCancelledError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.cancelled, + `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.` + ) } -// support IE9 -NavigationDuplicated._name = 'NavigationDuplicated' +export function createNavigationAbortedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.aborted, + `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.` + ) +} + +function createRouterError (from, to, type, message) { + const error = new Error(message) + error._isRouter = true + error.from = from + error.to = to + error.type = type + + const newStack = error.stack.split('\n') + newStack.splice(1, 2) // remove 2 last useless calls + error.stack = newStack.join('\n') + return error +} + +const propertiesToLog = ['params', 'query', 'hash'] + +function stringifyRoute (to) { + if (typeof to === 'string') return to + if ('path' in to) return to.path + const location = {} + for (const key of propertiesToLog) { + if (key in to) location[key] = to[key] + } + return JSON.stringify(location, null, 2) +} diff --git a/src/util/warn.js b/src/util/warn.js index 9e8982fb6..73e70caf8 100644 --- a/src/util/warn.js +++ b/src/util/warn.js @@ -16,10 +16,6 @@ export function isError (err: any): boolean { return Object.prototype.toString.call(err).indexOf('Error') > -1 } -export function isExtendedError (constructor: Function, err: any): boolean { - return ( - err instanceof constructor || - // _name is to support IE9 too - (err && (err.name === constructor.name || err._name === constructor._name)) - ) +export function isRouterError (err: any, errorType: ?string): boolean { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) } diff --git a/test/unit/specs/error-handling.spec.js b/test/unit/specs/error-handling.spec.js index 9daf5b172..8d41bca18 100644 --- a/test/unit/specs/error-handling.spec.js +++ b/test/unit/specs/error-handling.spec.js @@ -1,5 +1,6 @@ import Vue from 'vue' import VueRouter from '../../../src/index' +import { NavigationFailureType } from '../../../src/history/errors' Vue.use(VueRouter) @@ -40,6 +41,56 @@ describe('error handling', () => { }) }) + it('NavigationDuplicated error', done => { + const router = new VueRouter() + + router.push('/foo') + router.push('/foo').catch(err => { + expect(err.type).toBe(NavigationFailureType.duplicated) + done() + }) + }) + + it('NavigationCancelled error', done => { + const router = new VueRouter() + + router.beforeEach((to, from, next) => { + setTimeout(() => next(), 100) + }) + + router.push('/foo').catch(err => { + expect(err.type).toBe(NavigationFailureType.cancelled) + done() + }) + router.push('/') + }) + + it('NavigationRedirected error', done => { + const router = new VueRouter() + + router.beforeEach((to, from, next) => { + if (to.query.redirect) { + next(to.query.redirect) + } + }) + + router.push('/foo?redirect=/').catch(err => { + expect(err.type).toBe(NavigationFailureType.redirected) + done() + }) + }) + + it('NavigationAborted error', done => { + const router = new VueRouter() + + router.beforeEach((to, from, next) => { next(false) }) + + router.push('/foo').catch(err => { + expect(err.type).toBe(NavigationFailureType.aborted) + done() + }) + }) + it('async component errors', done => { spyOn(console, 'warn') const err = new Error('foo') From 1bc23f18c3b3842e4aca1e23796f0d00b8becf04 Mon Sep 17 00:00:00 2001 From: Louis-Marie Michelin <33673240+lmichelin@users.noreply.github.com> Date: Tue, 19 May 2020 15:44:25 +0200 Subject: [PATCH 04/15] refactor: missing renaming (#3204) --- src/history/abstract.js | 2 +- src/history/base.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/history/abstract.js b/src/history/abstract.js index 9437182e7..1add43d01 100644 --- a/src/history/abstract.js +++ b/src/history/abstract.js @@ -51,7 +51,7 @@ export class AbstractHistory extends History { this.updateRoute(route) }, err => { - if (isRouterError(err, NavigationFailureType.NAVIGATION_DUPLICATED)) { + if (isRouterError(err, NavigationFailureType.duplicated)) { this.index = targetIndex } } diff --git a/src/history/base.js b/src/history/base.js index 2b9d71cae..6911b71f6 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -114,7 +114,7 @@ export class History { // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isRouterError(err, NavigationFailureType.NAVIGATION_DUPLICATED) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.duplicated) && isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err) From f11e531521fdac396877767c68e30ae79ecb5d2c Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Wed, 20 May 2020 13:32:35 +0300 Subject: [PATCH 05/15] docs: add installation instructions using Vue CLI (#3206) [skip ci] * docs: add installation instructions using Vue CLI * Update docs/installation.md Co-authored-by: Eduardo San Martin Morote --- docs/installation.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index b64ecace3..59823e5bf 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -32,6 +32,14 @@ Vue.use(VueRouter) You don't need to do this when using global script tags. +## Vue CLI + +If you have a project using [Vue CLI](https://cli.vuejs.org/) you can add Vue Router as a plugin. You can let the CLI generate the code above for you as well as two sample routes. **It will also overwrite your `App.vue`** so make sure to backup up the file before running the following command inside your project: + +```sh +vue add router +``` + ## Dev Build You will have to clone directly from GitHub and build `vue-router` yourself if From 191277ffa5a0e1ce3ba1e0d5c917b711f18f3cd4 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 20 May 2020 12:41:46 +0200 Subject: [PATCH 06/15] ci: selectively ignore docs build --- netlify.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 netlify.toml diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..0293ef211 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[build] + ignore = "git diff --quiet HEAD^ HEAD ./docs/" + publish = "docs/.vuepress/dist" + command = "yarn run docs:build" + From c0e86fac415bbc5a1f3a430827b8ff48356909d3 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 20 May 2020 12:43:53 +0200 Subject: [PATCH 07/15] docs: typo --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 59823e5bf..5de3b4aa7 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -34,7 +34,7 @@ You don't need to do this when using global script tags. ## Vue CLI -If you have a project using [Vue CLI](https://cli.vuejs.org/) you can add Vue Router as a plugin. You can let the CLI generate the code above for you as well as two sample routes. **It will also overwrite your `App.vue`** so make sure to backup up the file before running the following command inside your project: +If you have a project using [Vue CLI](https://cli.vuejs.org/) you can add Vue Router as a plugin. You can let the CLI generate the code above for you as well as two sample routes. **It will also overwrite your `App.vue`** so make sure to backup the file before running the following command inside your project: ```sh vue add router From 231f4a5851372bd653ca6125bc00c025ef0ca77e Mon Sep 17 00:00:00 2001 From: Steven Chang <52058700+Steven-Chang1114@users.noreply.github.com> Date: Wed, 20 May 2020 19:39:55 +0800 Subject: [PATCH 08/15] docs: typo in node.js example (#3205) --- docs/guide/essentials/history-mode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/essentials/history-mode.md b/docs/guide/essentials/history-mode.md index 5eeaad033..2b2c50686 100644 --- a/docs/guide/essentials/history-mode.md +++ b/docs/guide/essentials/history-mode.md @@ -52,9 +52,9 @@ const fs = require('fs') const httpPort = 80 http.createServer((req, res) => { - fs.readFile('index.htm', 'utf-8', (err, content) => { + fs.readFile('index.html', 'utf-8', (err, content) => { if (err) { -      console.log('We cannot open "index.htm" file.') +      console.log('We cannot open "index.html" file.') } res.writeHead(200, { From ca68c6b71cafc2056f666b19f55f79303c0bb5fa Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 20 May 2020 15:17:50 +0200 Subject: [PATCH 09/15] chore: remove blank line [skip ci] --- netlify.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/netlify.toml b/netlify.toml index 0293ef211..7d078bba2 100644 --- a/netlify.toml +++ b/netlify.toml @@ -2,4 +2,3 @@ ignore = "git diff --quiet HEAD^ HEAD ./docs/" publish = "docs/.vuepress/dist" command = "yarn run docs:build" - From af75417485e990b04dd57ccf813bc8564cc52197 Mon Sep 17 00:00:00 2001 From: Jinjiang Date: Tue, 26 May 2020 17:31:45 +0800 Subject: [PATCH 10/15] docs(zh): update (#3178) --- docs/zh/README.md | 2 ++ docs/zh/api/README.md | 9 ++++++++ docs/zh/guide/README.md | 2 ++ docs/zh/guide/advanced/lazy-loading.md | 2 ++ docs/zh/guide/advanced/navigation-guards.md | 23 +++++++++++++++++-- docs/zh/guide/advanced/scroll-behavior.md | 2 ++ docs/zh/guide/advanced/transitions.md | 2 ++ docs/zh/guide/essentials/dynamic-matching.md | 18 ++++++++------- docs/zh/guide/essentials/history-mode.md | 2 ++ docs/zh/guide/essentials/named-routes.md | 2 ++ docs/zh/guide/essentials/nested-routes.md | 2 ++ docs/zh/guide/essentials/passing-props.md | 2 ++ .../zh/guide/essentials/redirect-and-alias.md | 2 +- 13 files changed, 59 insertions(+), 11 deletions(-) diff --git a/docs/zh/README.md b/docs/zh/README.md index d790fdb42..9e813c1a5 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -4,6 +4,8 @@ 对于 TypeScript 用户来说,`vue-router@3.0+` 依赖 `vue@2.5+`,反之亦然。 ::: + + Vue Router 是 [Vue.js](http://cn.vuejs.org) 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有: - 嵌套的路由/视图表 diff --git a/docs/zh/api/README.md b/docs/zh/api/README.md index 3cd404cc0..94c64cc40 100644 --- a/docs/zh/api/README.md +++ b/docs/zh/api/README.md @@ -166,6 +166,13 @@ sidebar: auto 配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项 `linkExactActiveClass` 进行全局配置的。 +### aria-current-value + +- 类型: `'page' | 'step' | 'location' | 'date' | 'time'` +- 默认值: `"page"` + + 当链接根据精确匹配规则激活时配置的 `aria-current` 的值。这个值应该是 ARIA 规范中[允许的 aria-current 的值](https://www.w3.org/TR/wai-aria-1.2/#aria-current)。在绝大多数场景下,默认值 `page` 应该是最合适的。 + ## `` `` 组件是一个 functional 组件,渲染路径匹配到的视图组件。`` 渲染的组件还可以内嵌自己的 ``,根据嵌套路径,渲染嵌套组件。 @@ -288,6 +295,8 @@ sidebar: auto - 类型: `boolean` +- 默认值: `true` + 当浏览器不支持 `history.pushState` 控制路由是否应该回退到 `hash` 模式。默认值为 `true`。 在 IE9 中,设置为 `false` 会使得每个 `router-link` 导航都触发整页刷新。它可用于工作在 IE9 下的服务端渲染应用,因为一个 hash 模式的 URL 并不支持服务端渲染。 diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md index b5d2ac53f..05134397e 100644 --- a/docs/zh/guide/README.md +++ b/docs/zh/guide/README.md @@ -6,6 +6,8 @@ 同时,所有的例子都将使用完整版的 Vue 以解析模板。更多细节请[移步这里](https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时)。 ::: + + 用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。下面是个基本例子: ## HTML diff --git a/docs/zh/guide/advanced/lazy-loading.md b/docs/zh/guide/advanced/lazy-loading.md index f87191ad7..51391c85b 100644 --- a/docs/zh/guide/advanced/lazy-loading.md +++ b/docs/zh/guide/advanced/lazy-loading.md @@ -1,5 +1,7 @@ # 路由懒加载 + + 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。 结合 Vue 的[异步组件](https://cn.vuejs.org/v2/guide/components-dynamic-async.html#异步组件)和 Webpack 的[代码分割功能](https://doc.webpack-china.org/guides/code-splitting-async/#require-ensure-/),轻松实现路由组件的懒加载。 diff --git a/docs/zh/guide/advanced/navigation-guards.md b/docs/zh/guide/advanced/navigation-guards.md index 162ddc0f6..21d7937b8 100644 --- a/docs/zh/guide/advanced/navigation-guards.md +++ b/docs/zh/guide/advanced/navigation-guards.md @@ -8,6 +8,8 @@ 记住**参数或查询的改变并不会触发进入/离开的导航守卫**。你可以通过[观察 `$route` 对象](../essentials/dynamic-matching.md#响应路由参数的变化)来应对这些变化,或使用 `beforeRouteUpdate` 的组件内守卫。 + + ## 全局前置守卫 你可以使用 `router.beforeEach` 注册一个全局前置守卫: @@ -38,7 +40,24 @@ router.beforeEach((to, from, next) => { - **`next(error)`**: (2.4.0+) 如果传入 `next` 的参数是一个 `Error` 实例,则导航会被终止且该错误会被传递给 [`router.onError()`](../../api/#router-onerror) 注册过的回调。 -**确保要调用 `next` 方法,否则钩子就不会被 resolved。** +**确保 `next` 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。**这里有一个在用户未能验证身份时重定向到 `/login` 的示例: + +```js +// BAD +router.beforeEach((to, from, next) => { + if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) + // 如果用户未能验证身份,则 `next` 会被调用两次 + next() +}) +``` + +```js +// GOOD +router.beforeEach((to, from, next) => { + if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) + else next() +}) +``` ## 全局解析守卫 @@ -143,7 +162,7 @@ beforeRouteLeave (to, from, next) { ## 完整的导航解析流程 1. 导航被触发。 -2. 在失活的组件里调用离开守卫。 +2. 在失活的组件里调用 `beforeRouteLeave` 守卫。 3. 调用全局的 `beforeEach` 守卫。 4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。 5. 在路由配置里调用 `beforeEnter`。 diff --git a/docs/zh/guide/advanced/scroll-behavior.md b/docs/zh/guide/advanced/scroll-behavior.md index c30670362..c354ff419 100644 --- a/docs/zh/guide/advanced/scroll-behavior.md +++ b/docs/zh/guide/advanced/scroll-behavior.md @@ -1,5 +1,7 @@ # 滚动行为 + + 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 `vue-router` 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。 **注意: 这个功能只在支持 `history.pushState` 的浏览器中可用。** diff --git a/docs/zh/guide/advanced/transitions.md b/docs/zh/guide/advanced/transitions.md index f71869df5..d1197b007 100644 --- a/docs/zh/guide/advanced/transitions.md +++ b/docs/zh/guide/advanced/transitions.md @@ -1,5 +1,7 @@ # 过渡动效 + + `` 是基本的动态组件,所以我们可以用 `` 组件给它添加一些过渡效果: ``` html diff --git a/docs/zh/guide/essentials/dynamic-matching.md b/docs/zh/guide/essentials/dynamic-matching.md index a5297a64f..9eec0b5bb 100644 --- a/docs/zh/guide/essentials/dynamic-matching.md +++ b/docs/zh/guide/essentials/dynamic-matching.md @@ -1,8 +1,10 @@ # 动态路由匹配 + + 我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 `User` 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 `vue-router` 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果: -``` js +```js const User = { template: '
    User
    ' } @@ -20,7 +22,7 @@ const router = new VueRouter({ 一个“路径参数”使用冒号 `:` 标记。当匹配到一个路由时,参数值会被设置到 `this.$route.params`,可以在每个组件内使用。于是,我们可以更新 `User` 的模板,输出当前用户的 ID: -``` js +```js const User = { template: '
    User {{ $route.params.id }}
    ' } @@ -31,8 +33,8 @@ const User = { 你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 `$route.params` 中。例如: | 模式 | 匹配路径 | $route.params | -|---------|------|--------| -| /user/:username | /user/evan | `{ username: 'evan' }` | +| ----------------------------- | ------------------- | -------------------------------------- | +| /user/:username | /user/evan | `{ username: 'evan' }` | | /user/:username/post/:post_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` | 除了 `$route.params` 外,`$route` 对象还提供了其它有用的信息,例如,`$route.query` (如果 URL 中有查询参数)、`$route.hash` 等等。你可以查看 [API 文档](../../api/#路由对象) 的详细说明。 @@ -43,11 +45,11 @@ const User = { 复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) `$route` 对象: -``` js +```js const User = { template: '...', watch: { - '$route' (to, from) { + $route(to, from) { // 对路由变化作出响应... } } @@ -56,7 +58,7 @@ const User = { 或者使用 2.2 中引入的 `beforeRouteUpdate` [导航守卫](../advanced/navigation-guards.html): -``` js +```js const User = { template: '...', beforeRouteUpdate (to, from, next) { @@ -96,7 +98,7 @@ this.$route.params.pathMatch // '/non-existing' ## 高级匹配模式 -`vue-router` 使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。查看它的 [文档](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#parameters) 学习高阶的路径匹配,还有 [这个例子 ](https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js) 展示 `vue-router` 怎么使用这类匹配。 +`vue-router` 使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。查看它的[文档](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#parameters)学习高阶的路径匹配,还有[这个例子 ](https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js)展示 `vue-router` 怎么使用这类匹配。 ## 匹配优先级 diff --git a/docs/zh/guide/essentials/history-mode.md b/docs/zh/guide/essentials/history-mode.md index ee27f0c56..9ef4a31ce 100644 --- a/docs/zh/guide/essentials/history-mode.md +++ b/docs/zh/guide/essentials/history-mode.md @@ -19,6 +19,8 @@ const router = new VueRouter({ ## 后端配置例子 +**注意**:下列示例假设你在根目录服务这个应用。如果想部署到一个子目录,你需要使用 [Vue CLI 的 `publicPath` 选项](https://cli.vuejs.org/zh/config/#publicpath) 和相关的 [router `base` property](https://router.vuejs.org/zh/api/#base)。你还需要把下列示例中的根目录调整成为子目录 (例如用 `RewriteBase /name-of-your-subfolder/` 替换掉 `RewriteBase /`)。 + #### Apache ```apache diff --git a/docs/zh/guide/essentials/named-routes.md b/docs/zh/guide/essentials/named-routes.md index 175eeefab..da4481cf6 100644 --- a/docs/zh/guide/essentials/named-routes.md +++ b/docs/zh/guide/essentials/named-routes.md @@ -1,5 +1,7 @@ # 命名路由 + + 有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 `routes` 配置中给某个路由设置名称。 ``` js diff --git a/docs/zh/guide/essentials/nested-routes.md b/docs/zh/guide/essentials/nested-routes.md index e9e0f2b84..8d0d15a40 100644 --- a/docs/zh/guide/essentials/nested-routes.md +++ b/docs/zh/guide/essentials/nested-routes.md @@ -1,5 +1,7 @@ # 嵌套路由 + + 实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如: ``` diff --git a/docs/zh/guide/essentials/passing-props.md b/docs/zh/guide/essentials/passing-props.md index 120a64dde..2d30d20f5 100644 --- a/docs/zh/guide/essentials/passing-props.md +++ b/docs/zh/guide/essentials/passing-props.md @@ -1,5 +1,7 @@ # 路由组件传参 + + 在组件中使用 `$route` 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。 使用 `props` 将组件和路由解耦: diff --git a/docs/zh/guide/essentials/redirect-and-alias.md b/docs/zh/guide/essentials/redirect-and-alias.md index 56a567946..a31810321 100644 --- a/docs/zh/guide/essentials/redirect-and-alias.md +++ b/docs/zh/guide/essentials/redirect-and-alias.md @@ -35,7 +35,7 @@ const router = new VueRouter({ }) ``` -注意[导航守卫](../advanced/navigation-guards.md)并没有应用在跳转路由上,而仅仅应用在其目标上。在下面这个例子中,为 `/a` 路由添加一个 `beforeEach` 或 `beforeLeave` 守卫并不会有任何效果。 +注意[导航守卫](../advanced/navigation-guards.md)并没有应用在跳转路由上,而仅仅应用在其目标上。在下面这个例子中,为 `/a` 路由添加一个 `beforeEach` 守卫并不会有任何效果。 其它高级用法,请参考[例子](https://github.com/vuejs/vue-router/blob/dev/examples/redirect/app.js)。 From 1575a1898c6deb26cdef801914fc1905590bc60f Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 26 May 2020 17:39:25 +0200 Subject: [PATCH 11/15] feat(url): call afterEach hooks after url is ensured (#2292) Closes #2079 This helps integrating with external scripts and makes more sense as the navigation is finished. However, onComplete callback passed to `router.push` is now called before `afterEach` hooks. This shouldn't be a problem as there's no guarantee provided anywhere in the docs about this --- src/history/base.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/history/base.js b/src/history/base.js index 6911b71f6..3a96bb3ba 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -81,9 +81,13 @@ export class History { this.confirmTransition( route, () => { + const prev = this.current this.updateRoute(route) onComplete && onComplete(route) this.ensureURL() + this.router.afterHooks.forEach(hook => { + hook && hook(route, prev) + }) // fire ready cbs once if (!this.ready) { @@ -214,12 +218,8 @@ export class History { } updateRoute (route: Route) { - const prev = this.current this.current = route this.cb && this.cb(route) - this.router.afterHooks.forEach(hook => { - hook && hook(route, prev) - }) } setupListeners () { From 166249a821182d3b93cf104277337a6d86329e00 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 27 May 2020 12:13:18 +0200 Subject: [PATCH 12/15] build: show version --- scripts/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 1afac163a..be596e04f 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,5 +1,6 @@ set -e -echo "Enter release version: " +echo "Current version:" $(grep version package.json | sed -E 's/^.*"(4[^"]+)".*$/\1/') +echo "Enter release version e.g. 3.3.0: " read VERSION read -p "Releasing v$VERSION - are you sure? (y/n)" -n 1 -r @@ -7,7 +8,6 @@ echo # (optional) move to a new line if [[ $REPLY =~ ^[Yy]$ ]] then echo "Releasing v$VERSION ..." - npm test # commit VERSION=$VERSION npm run build From c29460d66ee5cd2bad0ba5b5764ab3fc52854352 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 27 May 2020 12:18:56 +0200 Subject: [PATCH 13/15] refactor: use forEach --- src/history/errors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/history/errors.js b/src/history/errors.js index 4fac5725a..21f3f56f9 100644 --- a/src/history/errors.js +++ b/src/history/errors.js @@ -60,8 +60,8 @@ function stringifyRoute (to) { if (typeof to === 'string') return to if ('path' in to) return to.path const location = {} - for (const key of propertiesToLog) { + propertiesToLog.forEach(key => { if (key in to) location[key] = to[key] - } + }) return JSON.stringify(location, null, 2) } From e3332f2e290362a1640bfbdb9065d8c6f63980ac Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 27 May 2020 12:19:27 +0200 Subject: [PATCH 14/15] build: bundle 3.3.0 --- dist/vue-router.common.js | 240 +++++++++++++++++++---------- dist/vue-router.esm.browser.js | 231 ++++++++++++++++++--------- dist/vue-router.esm.browser.min.js | 4 +- dist/vue-router.esm.js | 240 +++++++++++++++++++---------- dist/vue-router.js | 240 +++++++++++++++++++---------- dist/vue-router.min.js | 4 +- 6 files changed, 640 insertions(+), 319 deletions(-) diff --git a/dist/vue-router.common.js b/dist/vue-router.common.js index 9a5d2ae73..0f9a1c772 100644 --- a/dist/vue-router.common.js +++ b/dist/vue-router.common.js @@ -1,5 +1,5 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ @@ -23,12 +23,8 @@ function isError (err) { return Object.prototype.toString.call(err).indexOf('Error') > -1 } -function isExtendedError (constructor, err) { - return ( - err instanceof constructor || - // _name is to support IE9 too - (err && (err.name === constructor.name || err._name === constructor._name)) - ) +function isRouterError (err, errorType) { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) } function extend (a, b) { @@ -1708,12 +1704,10 @@ function setupScroll () { var stateCopy = extend({}, window.history.state); stateCopy.key = getStateKey(); window.history.replaceState(stateCopy, '', absolutePath); - window.addEventListener('popstate', function (e) { - saveScrollPosition(); - if (e.state && e.state.key) { - setStateKey(e.state.key); - } - }); + window.addEventListener('popstate', handlePopState); + return function () { + window.removeEventListener('popstate', handlePopState); + } } function handleScroll ( @@ -1775,6 +1769,13 @@ function saveScrollPosition () { } } +function handlePopState (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } +} + function getScrollPosition () { var key = getStateKey(); if (key) { @@ -2014,32 +2015,73 @@ function once (fn) { } } -var NavigationDuplicated = /*@__PURE__*/(function (Error) { - function NavigationDuplicated (normalizedLocation) { - Error.call(this); - this.name = this._name = 'NavigationDuplicated'; - // passing the message to super() doesn't seem to work in the transpiled version - this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed"; - // add a stack property so services like Sentry can correctly display it - Object.defineProperty(this, 'stack', { - value: new Error().stack, - writable: true, - configurable: true - }); - // we could also have used - // Error.captureStackTrace(this, this.constructor) - // but it only exists on node and chrome - } +var NavigationFailureType = { + redirected: 1, + aborted: 2, + cancelled: 3, + duplicated: 4 +}; + +function createNavigationRedirectedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.redirected, + ("Redirected from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(to)) + "\" via a navigation guard.") + ) +} + +function createNavigationDuplicatedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.duplicated, + ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".") + ) +} + +function createNavigationCancelledError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.cancelled, + ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.") + ) +} + +function createNavigationAbortedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.aborted, + ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.") + ) +} + +function createRouterError (from, to, type, message) { + var error = new Error(message); + error._isRouter = true; + error.from = from; + error.to = to; + error.type = type; - if ( Error ) NavigationDuplicated.__proto__ = Error; - NavigationDuplicated.prototype = Object.create( Error && Error.prototype ); - NavigationDuplicated.prototype.constructor = NavigationDuplicated; + var newStack = error.stack.split('\n'); + newStack.splice(1, 2); // remove 2 last useless calls + error.stack = newStack.join('\n'); + return error +} - return NavigationDuplicated; -}(Error)); +var propertiesToLog = ['params', 'query', 'hash']; -// support IE9 -NavigationDuplicated._name = 'NavigationDuplicated'; +function stringifyRoute (to) { + if (typeof to === 'string') { return to } + if ('path' in to) { return to.path } + var location = {}; + propertiesToLog.forEach(function (key) { + if (key in to) { location[key] = to[key]; } + }); + return JSON.stringify(location, null, 2) +} /* */ @@ -2053,6 +2095,7 @@ var History = function History (router, base) { this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; + this.listeners = []; }; History.prototype.listen = function listen (cb) { @@ -2085,9 +2128,13 @@ History.prototype.transitionTo = function transitionTo ( this.confirmTransition( route, function () { + var prev = this$1.current; this$1.updateRoute(route); onComplete && onComplete(route); this$1.ensureURL(); + this$1.router.afterHooks.forEach(function (hook) { + hook && hook(route, prev); + }); // fire ready cbs once if (!this$1.ready) { @@ -2120,7 +2167,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.duplicated) && isError(err)) { if (this$1.errorCbs.length) { this$1.errorCbs.forEach(function (cb) { cb(err); @@ -2138,7 +2185,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl route.matched.length === current.matched.length ) { this.ensureURL(); - return abort(new NavigationDuplicated(route)) + return abort(createNavigationDuplicatedError(current, route)) } var ref = resolveQueue( @@ -2165,12 +2212,15 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { - return abort() + return abort(createNavigationCancelledError(current, route)) } try { hook(route, current, function (to) { - if (to === false || isError(to)) { + if (to === false) { // next(false) -> abort navigation, ensure current URL + this$1.ensureURL(true); + abort(createNavigationAbortedError(current, route)); + } else if (isError(to)) { this$1.ensureURL(true); abort(to); } else if ( @@ -2179,7 +2229,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect - abort(); + abort(createNavigationRedirectedError(current, route)); if (typeof to === 'object' && to.replace) { this$1.replace(to); } else { @@ -2220,12 +2270,19 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl }; History.prototype.updateRoute = function updateRoute (route) { - var prev = this.current; this.current = route; this.cb && this.cb(route); - this.router.afterHooks.forEach(function (hook) { - hook && hook(route, prev); +}; + +History.prototype.setupListeners = function setupListeners () { + // Default implementation is empty +}; + +History.prototype.teardownListeners = function teardownListeners () { + this.listeners.forEach(function (cleanupListener) { + cleanupListener(); }); + this.listeners = []; }; function normalizeBase (base) { @@ -2370,25 +2427,37 @@ function poll ( var HTML5History = /*@__PURE__*/(function (History) { function HTML5History (router, base) { + History.call(this, router, base); + + this._startLocation = getLocation(this.base); + } + + if ( History ) HTML5History.__proto__ = History; + HTML5History.prototype = Object.create( History && History.prototype ); + HTML5History.prototype.constructor = HTML5History; + + HTML5History.prototype.setupListeners = function setupListeners () { var this$1 = this; - History.call(this, router, base); + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - var initLocation = getLocation(this.base); - window.addEventListener('popstate', function (e) { + var handleRoutingEvent = function () { var current = this$1.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. var location = getLocation(this$1.base); - if (this$1.current === START && location === initLocation) { + if (this$1.current === START && location === this$1._startLocation) { return } @@ -2397,12 +2466,12 @@ var HTML5History = /*@__PURE__*/(function (History) { handleScroll(router, route, current, true); } }); + }; + window.addEventListener('popstate', handleRoutingEvent); + this.listeners.push(function () { + window.removeEventListener('popstate', handleRoutingEvent); }); - } - - if ( History ) HTML5History.__proto__ = History; - HTML5History.prototype = Object.create( History && History.prototype ); - HTML5History.prototype.constructor = HTML5History; + }; HTML5History.prototype.go = function go (n) { window.history.go(n); @@ -2475,31 +2544,40 @@ var HashHistory = /*@__PURE__*/(function (History) { HashHistory.prototype.setupListeners = function setupListeners () { var this$1 = this; + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - function () { - var current = this$1.current; - if (!ensureSlash()) { - return - } - this$1.transitionTo(getHash(), function (route) { - if (supportsScroll) { - handleScroll(this$1.router, route, current, true); - } - if (!supportsPushState) { - replaceHash(route.fullPath); - } - }); + var handleRoutingEvent = function () { + var current = this$1.current; + if (!ensureSlash()) { + return } + this$1.transitionTo(getHash(), function (route) { + if (supportsScroll) { + handleScroll(this$1.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + }; + var eventType = supportsPushState ? 'popstate' : 'hashchange'; + window.addEventListener( + eventType, + handleRoutingEvent ); + this.listeners.push(function () { + window.removeEventListener(eventType, handleRoutingEvent); + }); }; HashHistory.prototype.push = function push (location, onComplete, onAbort) { @@ -2672,7 +2750,7 @@ var AbstractHistory = /*@__PURE__*/(function (History) { this$1.updateRoute(route); }, function (err) { - if (isExtendedError(NavigationDuplicated, err)) { + if (isRouterError(err, NavigationFailureType.duplicated)) { this$1.index = targetIndex; } } @@ -2767,6 +2845,12 @@ VueRouter.prototype.init = function init (app /* Vue component instance */) { // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } + + if (!this$1.app) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this$1.history.teardownListeners(); + } }); // main app previously initialized @@ -2779,17 +2863,11 @@ VueRouter.prototype.init = function init (app /* Vue component instance */) { var history = this.history; - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()); - } else if (history instanceof HashHistory) { - var setupHashListener = function () { + if (history instanceof HTML5History || history instanceof HashHistory) { + var setupListeners = function () { history.setupListeners(); }; - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ); + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners); } history.listen(function (route) { @@ -2922,7 +3000,7 @@ function createHref (base, fullPath, mode) { } VueRouter.install = install; -VueRouter.version = '3.2.0'; +VueRouter.version = '3.3.0'; if (inBrowser && window.Vue) { window.Vue.use(VueRouter); diff --git a/dist/vue-router.esm.browser.js b/dist/vue-router.esm.browser.js index 18cda9d4a..c9aa21b81 100644 --- a/dist/vue-router.esm.browser.js +++ b/dist/vue-router.esm.browser.js @@ -1,5 +1,5 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ @@ -21,12 +21,8 @@ function isError (err) { return Object.prototype.toString.call(err).indexOf('Error') > -1 } -function isExtendedError (constructor, err) { - return ( - err instanceof constructor || - // _name is to support IE9 too - (err && (err.name === constructor.name || err._name === constructor._name)) - ) +function isRouterError (err, errorType) { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) } function extend (a, b) { @@ -1684,12 +1680,10 @@ function setupScroll () { const stateCopy = extend({}, window.history.state); stateCopy.key = getStateKey(); window.history.replaceState(stateCopy, '', absolutePath); - window.addEventListener('popstate', e => { - saveScrollPosition(); - if (e.state && e.state.key) { - setStateKey(e.state.key); - } - }); + window.addEventListener('popstate', handlePopState); + return () => { + window.removeEventListener('popstate', handlePopState); + } } function handleScroll ( @@ -1751,6 +1745,13 @@ function saveScrollPosition () { } } +function handlePopState (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } +} + function getScrollPosition () { const key = getStateKey(); if (key) { @@ -1987,28 +1988,73 @@ function once (fn) { } } -class NavigationDuplicated extends Error { - constructor (normalizedLocation) { - super(); - this.name = this._name = 'NavigationDuplicated'; - // passing the message to super() doesn't seem to work in the transpiled version - this.message = `Navigating to current location ("${ - normalizedLocation.fullPath - }") is not allowed`; - // add a stack property so services like Sentry can correctly display it - Object.defineProperty(this, 'stack', { - value: new Error().stack, - writable: true, - configurable: true - }); - // we could also have used - // Error.captureStackTrace(this, this.constructor) - // but it only exists on node and chrome - } +const NavigationFailureType = { + redirected: 1, + aborted: 2, + cancelled: 3, + duplicated: 4 +}; + +function createNavigationRedirectedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.redirected, + `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.` + ) +} + +function createNavigationDuplicatedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.duplicated, + `Avoided redundant navigation to current location: "${from.fullPath}".` + ) +} + +function createNavigationCancelledError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.cancelled, + `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.` + ) } -// support IE9 -NavigationDuplicated._name = 'NavigationDuplicated'; +function createNavigationAbortedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.aborted, + `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.` + ) +} + +function createRouterError (from, to, type, message) { + const error = new Error(message); + error._isRouter = true; + error.from = from; + error.to = to; + error.type = type; + + const newStack = error.stack.split('\n'); + newStack.splice(1, 2); // remove 2 last useless calls + error.stack = newStack.join('\n'); + return error +} + +const propertiesToLog = ['params', 'query', 'hash']; + +function stringifyRoute (to) { + if (typeof to === 'string') return to + if ('path' in to) return to.path + const location = {}; + propertiesToLog.forEach(key => { + if (key in to) location[key] = to[key]; + }); + return JSON.stringify(location, null, 2) +} /* */ @@ -2022,6 +2068,8 @@ class History { + + // implemented by sub-classes @@ -2029,6 +2077,7 @@ class History { + constructor (router, base) { this.router = router; @@ -2040,6 +2089,7 @@ class History { this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; + this.listeners = []; } listen (cb) { @@ -2070,9 +2120,13 @@ class History { this.confirmTransition( route, () => { + const prev = this.current; this.updateRoute(route); onComplete && onComplete(route); this.ensureURL(); + this.router.afterHooks.forEach(hook => { + hook && hook(route, prev); + }); // fire ready cbs once if (!this.ready) { @@ -2103,7 +2157,7 @@ class History { // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.duplicated) && isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err); @@ -2121,7 +2175,7 @@ class History { route.matched.length === current.matched.length ) { this.ensureURL(); - return abort(new NavigationDuplicated(route)) + return abort(createNavigationDuplicatedError(current, route)) } const { updated, deactivated, activated } = resolveQueue( @@ -2145,12 +2199,15 @@ class History { this.pending = route; const iterator = (hook, next) => { if (this.pending !== route) { - return abort() + return abort(createNavigationCancelledError(current, route)) } try { hook(route, current, (to) => { - if (to === false || isError(to)) { + if (to === false) { // next(false) -> abort navigation, ensure current URL + this.ensureURL(true); + abort(createNavigationAbortedError(current, route)); + } else if (isError(to)) { this.ensureURL(true); abort(to); } else if ( @@ -2159,7 +2216,7 @@ class History { (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect - abort(); + abort(createNavigationRedirectedError(current, route)); if (typeof to === 'object' && to.replace) { this.replace(to); } else { @@ -2200,12 +2257,19 @@ class History { } updateRoute (route) { - const prev = this.current; this.current = route; this.cb && this.cb(route); - this.router.afterHooks.forEach(hook => { - hook && hook(route, prev); + } + + setupListeners () { + // Default implementation is empty + } + + teardownListeners () { + this.listeners.forEach(cleanupListener => { + cleanupListener(); }); + this.listeners = []; } } @@ -2350,24 +2414,34 @@ function poll ( /* */ class HTML5History extends History { + + constructor (router, base) { super(router, base); + this._startLocation = getLocation(this.base); + } + + setupListeners () { + if (this.listeners.length > 0) { + return + } + + const router = this.router; const expectScroll = router.options.scrollBehavior; const supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - const initLocation = getLocation(this.base); - window.addEventListener('popstate', e => { + const handleRoutingEvent = () => { const current = this.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base); - if (this.current === START && location === initLocation) { + if (this.current === START && location === this._startLocation) { return } @@ -2376,6 +2450,10 @@ class HTML5History extends History { handleScroll(router, route, current, true); } }); + }; + window.addEventListener('popstate', handleRoutingEvent); + this.listeners.push(() => { + window.removeEventListener('popstate', handleRoutingEvent); }); } @@ -2436,31 +2514,40 @@ class HashHistory extends History { // this is delayed until the app mounts // to avoid the hashchange listener being fired too early setupListeners () { + if (this.listeners.length > 0) { + return + } + const router = this.router; const expectScroll = router.options.scrollBehavior; const supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - () => { - const current = this.current; - if (!ensureSlash()) { - return - } - this.transitionTo(getHash(), route => { - if (supportsScroll) { - handleScroll(this.router, route, current, true); - } - if (!supportsPushState) { - replaceHash(route.fullPath); - } - }); + const handleRoutingEvent = () => { + const current = this.current; + if (!ensureSlash()) { + return } + this.transitionTo(getHash(), route => { + if (supportsScroll) { + handleScroll(this.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + }; + const eventType = supportsPushState ? 'popstate' : 'hashchange'; + window.addEventListener( + eventType, + handleRoutingEvent ); + this.listeners.push(() => { + window.removeEventListener(eventType, handleRoutingEvent); + }); } push (location, onComplete, onAbort) { @@ -2618,7 +2705,7 @@ class AbstractHistory extends History { this.updateRoute(route); }, err => { - if (isExtendedError(NavigationDuplicated, err)) { + if (isRouterError(err, NavigationFailureType.duplicated)) { this.index = targetIndex; } } @@ -2722,6 +2809,12 @@ class VueRouter { // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this.app === app) this.app = this.apps[0] || null; + + if (!this.app) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this.history.teardownListeners(); + } }); // main app previously initialized @@ -2734,17 +2827,11 @@ class VueRouter { const history = this.history; - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()); - } else if (history instanceof HashHistory) { - const setupHashListener = () => { + if (history instanceof HTML5History || history instanceof HashHistory) { + const setupListeners = () => { history.setupListeners(); }; - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ); + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners); } history.listen(route => { @@ -2872,7 +2959,7 @@ function createHref (base, fullPath, mode) { } VueRouter.install = install; -VueRouter.version = '3.2.0'; +VueRouter.version = '3.3.0'; if (inBrowser && window.Vue) { window.Vue.use(VueRouter); diff --git a/dist/vue-router.esm.browser.min.js b/dist/vue-router.esm.browser.min.js index 81d638b28..b98931f68 100644 --- a/dist/vue-router.esm.browser.min.js +++ b/dist/vue-router.esm.browser.min.js @@ -1,6 +1,6 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ -function t(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function e(t,e){return e instanceof t||e&&(e.name===t.name||e._name===t._name)}function n(t,e){for(const n in e)t[n]=e[n];return t}var r={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render(t,{props:e,children:r,parent:i,data:s}){s.routerView=!0;const a=i.$createElement,c=e.name,u=i.$route,h=i._routerViewCache||(i._routerViewCache={});let p=0,l=!1;for(;i&&i._routerRoot!==i;){const t=i.$vnode?i.$vnode.data:{};t.routerView&&p++,t.keepAlive&&i._directInactive&&i._inactive&&(l=!0),i=i.$parent}if(s.routerViewDepth=p,l){const t=h[c],e=t&&t.component;return e?(t.configProps&&o(e,s,t.route,t.configProps),a(e,s,r)):a()}const f=u.matched[p],d=f&&f.components[c];if(!f||!d)return h[c]=null,a();h[c]={component:d},s.registerRouteInstance=(t,e)=>{const n=f.instances[c];(e&&n!==t||!e&&n===t)&&(f.instances[c]=e)},(s.hook||(s.hook={})).prepatch=(t,e)=>{f.instances[c]=e.componentInstance},s.hook.init=t=>{t.data.keepAlive&&t.componentInstance&&t.componentInstance!==f.instances[c]&&(f.instances[c]=t.componentInstance)};const y=f.props&&f.props[c];return y&&(n(h[c],{route:u,configProps:y}),o(d,s,u,y)),a(d,s,r)}};function o(t,e,r,o){let i=e.props=function(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0}}(r,o);if(i){i=e.props=n({},i);const r=e.attrs=e.attrs||{};for(const e in i)t.props&&e in t.props||(r[e]=i[e],delete i[e])}}const i=/[!'()*]/g,s=t=>"%"+t.charCodeAt(0).toString(16),a=/%2C/g,c=t=>encodeURIComponent(t).replace(i,s).replace(a,","),u=decodeURIComponent;function h(t){const e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach(t=>{const n=t.replace(/\+/g," ").split("="),r=u(n.shift()),o=n.length>0?u(n.join("=")):null;void 0===e[r]?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]}),e):e}function p(t){const e=t?Object.keys(t).map(e=>{const n=t[e];if(void 0===n)return"";if(null===n)return c(e);if(Array.isArray(n)){const t=[];return n.forEach(n=>{void 0!==n&&(null===n?t.push(c(e)):t.push(c(e)+"="+c(n)))}),t.join("&")}return c(e)+"="+c(n)}).filter(t=>t.length>0).join("&"):null;return e?`?${e}`:""}const l=/\/?$/;function f(t,e,n,r){const o=r&&r.options.stringifyQuery;let i=e.query||{};try{i=d(i)}catch(t){}const s={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:g(e,o),matched:t?m(t):[]};return n&&(s.redirectedFrom=g(n,o)),Object.freeze(s)}function d(t){if(Array.isArray(t))return t.map(d);if(t&&"object"==typeof t){const e={};for(const n in t)e[n]=d(t[n]);return e}return t}const y=f(null,{path:"/"});function m(t){const e=[];for(;t;)e.unshift(t),t=t.parent;return e}function g({path:t,query:e={},hash:n=""},r){return(t||"/")+(r||p)(e)+n}function w(t,e){return e===y?t===e:!!e&&(t.path&&e.path?t.path.replace(l,"")===e.path.replace(l,"")&&t.hash===e.hash&&b(t.query,e.query):!(!t.name||!e.name)&&(t.name===e.name&&t.hash===e.hash&&b(t.query,e.query)&&b(t.params,e.params)))}function b(t={},e={}){if(!t||!e)return t===e;const n=Object.keys(t),r=Object.keys(e);return n.length===r.length&&n.every(n=>{const r=t[n],o=e[n];return"object"==typeof r&&"object"==typeof o?b(r,o):String(r)===String(o)})}function v(t,e,n){const r=t.charAt(0);if("/"===r)return t;if("?"===r||"#"===r)return e+t;const o=e.split("/");n&&o[o.length-1]||o.pop();const i=t.replace(/^\//,"").split("/");for(let t=0;t=0&&(e=t.slice(r),t=t.slice(0,r));const o=t.indexOf("?");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}(i.path||""),a=e&&e.path||"/",c=s.path?v(s.path,a,r||i.append):a,u=function(t,e={},n){const r=n||h;let o;try{o=r(t||"")}catch(t){o={}}for(const t in e)o[t]=e[t];return o}(s.query,i.query,o&&o.options.parseQuery);let p=i.hash||s.hash;return p&&"#"!==p.charAt(0)&&(p=`#${p}`),{_normalized:!0,path:c,query:u,hash:p}}const H=[String,Object],z=[String,Array],D=()=>{};var F={name:"RouterLink",props:{to:{type:H,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:z,default:"click"}},render(t){const e=this.$router,r=this.$route,{location:o,route:i,href:s}=e.resolve(this.to,r,this.append),a={},c=e.options.linkActiveClass,u=e.options.linkExactActiveClass,h=null==c?"router-link-active":c,p=null==u?"router-link-exact-active":u,d=null==this.activeClass?h:this.activeClass,y=null==this.exactActiveClass?p:this.exactActiveClass,m=i.redirectedFrom?f(null,B(i.redirectedFrom),null,e):i;a[y]=w(r,m),a[d]=this.exact?a[y]:function(t,e){return 0===t.path.replace(l,"/").indexOf(e.path.replace(l,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(const n in e)if(!(n in t))return!1;return!0}(t.query,e.query)}(r,m);const g=a[y]?this.ariaCurrentValue:null,b=t=>{N(t)&&(this.replace?e.replace(o,D):e.push(o,D))},v={click:N};Array.isArray(this.event)?this.event.forEach(t=>{v[t]=b}):v[this.event]=b;const x={class:a},k=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:s,route:i,navigate:b,isActive:a[d],isExactActive:a[y]});if(k){if(1===k.length)return k[0];if(k.length>1||!k.length)return 0===k.length?t():t("span",{},k)}if("a"===this.tag)x.on=v,x.attrs={href:s,"aria-current":g};else{const t=function t(e){if(e){let n;for(let r=0;r{!function t(e,n,r,o,i,s){const{path:a,name:c}=o;const u=o.pathToRegexpOptions||{};const h=function(t,e,n){n||(t=t.replace(/\/$/,""));return"/"===t[0]?t:null==e?t:x(`${e.path}/${t}`)}(a,i,u.strict);"boolean"==typeof o.caseSensitive&&(u.sensitive=o.caseSensitive);const p={path:h,regex:X(h,u),components:o.components||{default:o.component},instances:{},name:c,parent:i,matchAs:s,redirect:o.redirect,beforeEnter:o.beforeEnter,meta:o.meta||{},props:null==o.props?{}:o.components?o.props:{default:o.props}};o.children&&o.children.forEach(o=>{const i=s?x(`${s}/${o.path}`):void 0;t(e,n,r,o,p,i)});n[p.path]||(e.push(p.path),n[p.path]=p);if(void 0!==o.alias){const s=Array.isArray(o.alias)?o.alias:[o.alias];for(let a=0;a!t.optional).map(t=>t.name);if("object"!=typeof c.params&&(c.params={}),i&&"object"==typeof i.params)for(const t in i.params)!(t in c.params)&&e.indexOf(t)>-1&&(c.params[t]=i.params[t]);return c.path=V(t.path,c.params),a(t,c,s)}if(c.path){c.params={};for(let t=0;t{st(),t.state&&t.state.key&&nt(t.state.key)})}function it(t,e,n,r){if(!t.app)return;const o=t.options.scrollBehavior;o&&t.app.$nextTick(()=>{const i=function(){const t=et();if(t)return rt[t]}(),s=o.call(t,e,n,r?i:null);s&&("function"==typeof s.then?s.then(t=>{pt(t,i)}).catch(t=>{}):pt(s,i))})}function st(){const t=et();t&&(rt[t]={x:window.pageXOffset,y:window.pageYOffset})}function at(t){return ut(t.x)||ut(t.y)}function ct(t){return{x:ut(t.x)?t.x:window.pageXOffset,y:ut(t.y)?t.y:window.pageYOffset}}function ut(t){return"number"==typeof t}const ht=/^#\d/;function pt(t,e){const n="object"==typeof t;if(n&&"string"==typeof t.selector){const n=ht.test(t.selector)?document.getElementById(t.selector.slice(1)):document.querySelector(t.selector);if(n){let o=t.offset&&"object"==typeof t.offset?t.offset:{};e=function(t,e){const n=document.documentElement.getBoundingClientRect(),r=t.getBoundingClientRect();return{x:r.left-n.left-e.x,y:r.top-n.top-e.y}}(n,o={x:ut((r=o).x)?r.x:0,y:ut(r.y)?r.y:0})}else at(t)&&(e=ct(t))}else n&&at(t)&&(e=ct(t));var r;e&&window.scrollTo(e.x,e.y)}const lt=J&&function(){const t=window.navigator.userAgent;return(-1===t.indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&(window.history&&"function"==typeof window.history.pushState)}();function ft(t,e){st();const r=window.history;try{if(e){const e=n({},r.state);e.key=et(),r.replaceState(e,"",t)}else r.pushState({key:nt(Z())},"",t)}catch(n){window.location[e?"replace":"assign"](t)}}function dt(t){ft(t,!0)}function yt(t,e,n){const r=o=>{o>=t.length?n():t[o]?e(t[o],()=>{r(o+1)}):r(o+1)};r(0)}function mt(e){return(n,r,o)=>{let i=!1,s=0,a=null;gt(e,(e,n,r,c)=>{if("function"==typeof e&&void 0===e.cid){i=!0,s++;const n=vt(t=>{(function(t){return t.__esModule||bt&&"Module"===t[Symbol.toStringTag]})(t)&&(t=t.default),e.resolved="function"==typeof t?t:K.extend(t),r.components[c]=t,--s<=0&&o()}),u=vt(e=>{const n=`Failed to resolve async component ${c}: ${e}`;a||(a=t(e)?e:new Error(n),o(a))});let h;try{h=e(n,u)}catch(t){u(t)}if(h)if("function"==typeof h.then)h.then(n,u);else{const t=h.component;t&&"function"==typeof t.then&&t.then(n,u)}}}),i||o()}}function gt(t,e){return wt(t.map(t=>Object.keys(t.components).map(n=>e(t.components[n],t.instances[n],t,n))))}function wt(t){return Array.prototype.concat.apply([],t)}const bt="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function vt(t){let e=!1;return function(...n){if(!e)return e=!0,t.apply(this,n)}}class xt extends Error{constructor(t){super(),this.name=this._name="NavigationDuplicated",this.message=`Navigating to current location ("${t.fullPath}") is not allowed`,Object.defineProperty(this,"stack",{value:(new Error).stack,writable:!0,configurable:!0})}}xt._name="NavigationDuplicated";class kt{constructor(t,e){this.router=t,this.base=function(t){if(!t)if(J){const e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";"/"!==t.charAt(0)&&(t="/"+t);return t.replace(/\/$/,"")}(e),this.current=y,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]}listen(t){this.cb=t}onReady(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))}onError(t){this.errorCbs.push(t)}transitionTo(t,e,n){const r=this.router.match(t,this.current);this.confirmTransition(r,()=>{this.updateRoute(r),e&&e(r),this.ensureURL(),this.ready||(this.ready=!0,this.readyCbs.forEach(t=>{t(r)}))},t=>{n&&n(t),t&&!this.ready&&(this.ready=!0,this.readyErrorCbs.forEach(e=>{e(t)}))})}confirmTransition(n,r,o){const i=this.current,s=n=>{!e(xt,n)&&t(n)&&(this.errorCbs.length?this.errorCbs.forEach(t=>{t(n)}):console.error(n)),o&&o(n)};if(w(n,i)&&n.matched.length===i.matched.length)return this.ensureURL(),s(new xt(n));const{updated:a,deactivated:c,activated:u}=function(t,e){let n;const r=Math.max(t.length,e.length);for(n=0;nt.beforeEnter),mt(u));this.pending=n;const p=(e,r)=>{if(this.pending!==n)return s();try{e(n,i,e=>{!1===e||t(e)?(this.ensureURL(!0),s(e)):"string"==typeof e||"object"==typeof e&&("string"==typeof e.path||"string"==typeof e.name)?(s(),"object"==typeof e&&e.replace?this.replace(e):this.push(e)):r(e)})}catch(t){s(t)}};yt(h,p,()=>{const t=[];yt(function(t,e,n){return Rt(t,"beforeRouteEnter",(t,r,o,i)=>(function(t,e,n,r,o){return function(i,s,a){return t(i,s,t=>{"function"==typeof t&&r.push(()=>{!function t(e,n,r,o){n[r]&&!n[r]._isBeingDestroyed?e(n[r]):o()&&setTimeout(()=>{t(e,n,r,o)},16)}(t,e.instances,n,o)}),a(t)})}})(t,o,i,e,n))}(u,t,()=>this.current===n).concat(this.router.resolveHooks),p,()=>{if(this.pending!==n)return s();this.pending=null,r(n),this.router.app&&this.router.app.$nextTick(()=>{t.forEach(t=>{t()})})})})}updateRoute(t){const e=this.current;this.current=t,this.cb&&this.cb(t),this.router.afterHooks.forEach(n=>{n&&n(t,e)})}}function Rt(t,e,n,r){const o=gt(t,(t,r,o,i)=>{const s=function(t,e){"function"!=typeof t&&(t=K.extend(t));return t.options[e]}(t,e);if(s)return Array.isArray(s)?s.map(t=>n(t,r,o,i)):n(s,r,o,i)});return wt(r?o.reverse():o)}function Et(t,e){if(e)return function(){return t.apply(e,arguments)}}class At extends kt{constructor(t,e){super(t,e);const n=t.options.scrollBehavior,r=lt&&n;r&&ot();const o=Ct(this.base);window.addEventListener("popstate",e=>{const n=this.current,i=Ct(this.base);this.current===y&&i===o||this.transitionTo(i,e=>{r&&it(t,e,n,!0)})})}go(t){window.history.go(t)}push(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{ft(x(this.base+t.fullPath)),it(this.router,t,r,!1),e&&e(t)},n)}replace(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{dt(x(this.base+t.fullPath)),it(this.router,t,r,!1),e&&e(t)},n)}ensureURL(t){if(Ct(this.base)!==this.current.fullPath){const e=x(this.base+this.current.fullPath);t?ft(e):dt(e)}}getCurrentLocation(){return Ct(this.base)}}function Ct(t){let e=decodeURI(window.location.pathname);return t&&0===e.toLowerCase().indexOf(t.toLowerCase())&&(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}class $t extends kt{constructor(t,e,n){super(t,e),n&&function(t){const e=Ct(t);if(!/^\/#/.test(e))return window.location.replace(x(t+"/#"+e)),!0}(this.base)||Ot()}setupListeners(){const t=this.router.options.scrollBehavior,e=lt&&t;e&&ot(),window.addEventListener(lt?"popstate":"hashchange",()=>{const t=this.current;Ot()&&this.transitionTo(jt(),n=>{e&&it(this.router,n,t,!0),lt||_t(n.fullPath)})})}push(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{Tt(t.fullPath),it(this.router,t,r,!1),e&&e(t)},n)}replace(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{_t(t.fullPath),it(this.router,t,r,!1),e&&e(t)},n)}go(t){window.history.go(t)}ensureURL(t){const e=this.current.fullPath;jt()!==e&&(t?Tt(e):_t(e))}getCurrentLocation(){return jt()}}function Ot(){const t=jt();return"/"===t.charAt(0)||(_t("/"+t),!1)}function jt(){let t=window.location.href;const e=t.indexOf("#");if(e<0)return"";const n=(t=t.slice(e+1)).indexOf("?");if(n<0){const e=t.indexOf("#");t=e>-1?decodeURI(t.slice(0,e))+t.slice(e):decodeURI(t)}else t=decodeURI(t.slice(0,n))+t.slice(n);return t}function St(t){const e=window.location.href,n=e.indexOf("#");return`${n>=0?e.slice(0,n):e}#${t}`}function Tt(t){lt?ft(St(t)):window.location.hash=t}function _t(t){lt?dt(St(t)):window.location.replace(St(t))}class Pt extends kt{constructor(t,e){super(t,e),this.stack=[],this.index=-1}push(t,e,n){this.transitionTo(t,t=>{this.stack=this.stack.slice(0,this.index+1).concat(t),this.index++,e&&e(t)},n)}replace(t,e,n){this.transitionTo(t,t=>{this.stack=this.stack.slice(0,this.index).concat(t),e&&e(t)},n)}go(t){const n=this.index+t;if(n<0||n>=this.stack.length)return;const r=this.stack[n];this.confirmTransition(r,()=>{this.index=n,this.updateRoute(r)},t=>{e(xt,t)&&(this.index=n)})}getCurrentLocation(){const t=this.stack[this.stack.length-1];return t?t.fullPath:"/"}ensureURL(){}}class Lt{constructor(t={}){this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Y(t.routes||[],this);let e=t.mode||"hash";switch(this.fallback="history"===e&&!lt&&!1!==t.fallback,this.fallback&&(e="hash"),J||(e="abstract"),this.mode=e,e){case"history":this.history=new At(this,t.base);break;case"hash":this.history=new $t(this,t.base,this.fallback);break;case"abstract":this.history=new Pt(this,t.base)}}match(t,e,n){return this.matcher.match(t,e,n)}get currentRoute(){return this.history&&this.history.current}init(t){if(this.apps.push(t),t.$once("hook:destroyed",()=>{const e=this.apps.indexOf(t);e>-1&&this.apps.splice(e,1),this.app===t&&(this.app=this.apps[0]||null)}),this.app)return;this.app=t;const e=this.history;if(e instanceof At)e.transitionTo(e.getCurrentLocation());else if(e instanceof $t){const t=()=>{e.setupListeners()};e.transitionTo(e.getCurrentLocation(),t,t)}e.listen(t=>{this.apps.forEach(e=>{e._route=t})})}beforeEach(t){return qt(this.beforeHooks,t)}beforeResolve(t){return qt(this.resolveHooks,t)}afterEach(t){return qt(this.afterHooks,t)}onReady(t,e){this.history.onReady(t,e)}onError(t){this.history.onError(t)}push(t,e,n){if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((e,n)=>{this.history.push(t,e,n)});this.history.push(t,e,n)}replace(t,e,n){if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((e,n)=>{this.history.replace(t,e,n)});this.history.replace(t,e,n)}go(t){this.history.go(t)}back(){this.go(-1)}forward(){this.go(1)}getMatchedComponents(t){const e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map(t=>Object.keys(t.components).map(e=>t.components[e]))):[]}resolve(t,e,n){const r=B(t,e=e||this.history.current,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath;return{location:r,route:o,href:function(t,e,n){var r="hash"===n?"#"+e:e;return t?x(t+"/"+r):r}(this.history.base,i,this.mode),normalizedTo:r,resolved:o}}addRoutes(t){this.matcher.addRoutes(t),this.history.current!==y&&this.history.transitionTo(this.history.getCurrentLocation())}}function qt(t,e){return t.push(e),()=>{const n=t.indexOf(e);n>-1&&t.splice(n,1)}}Lt.install=function t(e){if(t.installed&&K===e)return;t.installed=!0,K=e;const n=t=>void 0!==t,o=(t,e)=>{let r=t.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(t,e)};e.mixin({beforeCreate(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,o(this,this)},destroyed(){o(this)}}),Object.defineProperty(e.prototype,"$router",{get(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get(){return this._routerRoot._route}}),e.component("RouterView",r),e.component("RouterLink",F);const i=e.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created},Lt.version="3.2.0",J&&window.Vue&&window.Vue.use(Lt);export default Lt; \ No newline at end of file +function t(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function e(e,n){return t(e)&&e._isRouter&&(null==n||e.type===n)}function n(t,e){for(const n in e)t[n]=e[n];return t}var r={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render(t,{props:e,children:r,parent:i,data:s}){s.routerView=!0;const a=i.$createElement,c=e.name,u=i.$route,h=i._routerViewCache||(i._routerViewCache={});let p=0,l=!1;for(;i&&i._routerRoot!==i;){const t=i.$vnode?i.$vnode.data:{};t.routerView&&p++,t.keepAlive&&i._directInactive&&i._inactive&&(l=!0),i=i.$parent}if(s.routerViewDepth=p,l){const t=h[c],e=t&&t.component;return e?(t.configProps&&o(e,s,t.route,t.configProps),a(e,s,r)):a()}const f=u.matched[p],d=f&&f.components[c];if(!f||!d)return h[c]=null,a();h[c]={component:d},s.registerRouteInstance=(t,e)=>{const n=f.instances[c];(e&&n!==t||!e&&n===t)&&(f.instances[c]=e)},(s.hook||(s.hook={})).prepatch=(t,e)=>{f.instances[c]=e.componentInstance},s.hook.init=t=>{t.data.keepAlive&&t.componentInstance&&t.componentInstance!==f.instances[c]&&(f.instances[c]=t.componentInstance)};const y=f.props&&f.props[c];return y&&(n(h[c],{route:u,configProps:y}),o(d,s,u,y)),a(d,s,r)}};function o(t,e,r,o){let i=e.props=function(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0}}(r,o);if(i){i=e.props=n({},i);const r=e.attrs=e.attrs||{};for(const e in i)t.props&&e in t.props||(r[e]=i[e],delete i[e])}}const i=/[!'()*]/g,s=t=>"%"+t.charCodeAt(0).toString(16),a=/%2C/g,c=t=>encodeURIComponent(t).replace(i,s).replace(a,","),u=decodeURIComponent;function h(t){const e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach(t=>{const n=t.replace(/\+/g," ").split("="),r=u(n.shift()),o=n.length>0?u(n.join("=")):null;void 0===e[r]?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]}),e):e}function p(t){const e=t?Object.keys(t).map(e=>{const n=t[e];if(void 0===n)return"";if(null===n)return c(e);if(Array.isArray(n)){const t=[];return n.forEach(n=>{void 0!==n&&(null===n?t.push(c(e)):t.push(c(e)+"="+c(n)))}),t.join("&")}return c(e)+"="+c(n)}).filter(t=>t.length>0).join("&"):null;return e?`?${e}`:""}const l=/\/?$/;function f(t,e,n,r){const o=r&&r.options.stringifyQuery;let i=e.query||{};try{i=d(i)}catch(t){}const s={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:g(e,o),matched:t?m(t):[]};return n&&(s.redirectedFrom=g(n,o)),Object.freeze(s)}function d(t){if(Array.isArray(t))return t.map(d);if(t&&"object"==typeof t){const e={};for(const n in t)e[n]=d(t[n]);return e}return t}const y=f(null,{path:"/"});function m(t){const e=[];for(;t;)e.unshift(t),t=t.parent;return e}function g({path:t,query:e={},hash:n=""},r){return(t||"/")+(r||p)(e)+n}function w(t,e){return e===y?t===e:!!e&&(t.path&&e.path?t.path.replace(l,"")===e.path.replace(l,"")&&t.hash===e.hash&&v(t.query,e.query):!(!t.name||!e.name)&&(t.name===e.name&&t.hash===e.hash&&v(t.query,e.query)&&v(t.params,e.params)))}function v(t={},e={}){if(!t||!e)return t===e;const n=Object.keys(t),r=Object.keys(e);return n.length===r.length&&n.every(n=>{const r=t[n],o=e[n];return"object"==typeof r&&"object"==typeof o?v(r,o):String(r)===String(o)})}function b(t,e,n){const r=t.charAt(0);if("/"===r)return t;if("?"===r||"#"===r)return e+t;const o=e.split("/");n&&o[o.length-1]||o.pop();const i=t.replace(/^\//,"").split("/");for(let t=0;t=0&&(e=t.slice(r),t=t.slice(0,r));const o=t.indexOf("?");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}(i.path||""),a=e&&e.path||"/",c=s.path?b(s.path,a,r||i.append):a,u=function(t,e={},n){const r=n||h;let o;try{o=r(t||"")}catch(t){o={}}for(const t in e)o[t]=e[t];return o}(s.query,i.query,o&&o.options.parseQuery);let p=i.hash||s.hash;return p&&"#"!==p.charAt(0)&&(p=`#${p}`),{_normalized:!0,path:c,query:u,hash:p}}const H=[String,Object],z=[String,Array],F=()=>{};var N={name:"RouterLink",props:{to:{type:H,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:z,default:"click"}},render(t){const e=this.$router,r=this.$route,{location:o,route:i,href:s}=e.resolve(this.to,r,this.append),a={},c=e.options.linkActiveClass,u=e.options.linkExactActiveClass,h=null==c?"router-link-active":c,p=null==u?"router-link-exact-active":u,d=null==this.activeClass?h:this.activeClass,y=null==this.exactActiveClass?p:this.exactActiveClass,m=i.redirectedFrom?f(null,B(i.redirectedFrom),null,e):i;a[y]=w(r,m),a[d]=this.exact?a[y]:function(t,e){return 0===t.path.replace(l,"/").indexOf(e.path.replace(l,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(const n in e)if(!(n in t))return!1;return!0}(t.query,e.query)}(r,m);const g=a[y]?this.ariaCurrentValue:null,v=t=>{D(t)&&(this.replace?e.replace(o,F):e.push(o,F))},b={click:D};Array.isArray(this.event)?this.event.forEach(t=>{b[t]=v}):b[this.event]=v;const x={class:a},k=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:s,route:i,navigate:v,isActive:a[d],isExactActive:a[y]});if(k){if(1===k.length)return k[0];if(k.length>1||!k.length)return 0===k.length?t():t("span",{},k)}if("a"===this.tag)x.on=b,x.attrs={href:s,"aria-current":g};else{const t=function t(e){if(e){let n;for(let r=0;r{!function t(e,n,r,o,i,s){const{path:a,name:c}=o;const u=o.pathToRegexpOptions||{};const h=function(t,e,n){n||(t=t.replace(/\/$/,""));return"/"===t[0]?t:null==e?t:x(`${e.path}/${t}`)}(a,i,u.strict);"boolean"==typeof o.caseSensitive&&(u.sensitive=o.caseSensitive);const p={path:h,regex:X(h,u),components:o.components||{default:o.component},instances:{},name:c,parent:i,matchAs:s,redirect:o.redirect,beforeEnter:o.beforeEnter,meta:o.meta||{},props:null==o.props?{}:o.components?o.props:{default:o.props}};o.children&&o.children.forEach(o=>{const i=s?x(`${s}/${o.path}`):void 0;t(e,n,r,o,p,i)});n[p.path]||(e.push(p.path),n[p.path]=p);if(void 0!==o.alias){const s=Array.isArray(o.alias)?o.alias:[o.alias];for(let a=0;a!t.optional).map(t=>t.name);if("object"!=typeof c.params&&(c.params={}),i&&"object"==typeof i.params)for(const t in i.params)!(t in c.params)&&e.indexOf(t)>-1&&(c.params[t]=i.params[t]);return c.path=V(t.path,c.params),a(t,c,s)}if(c.path){c.params={};for(let t=0;t{window.removeEventListener("popstate",at)}}function it(t,e,n,r){if(!t.app)return;const o=t.options.scrollBehavior;o&&t.app.$nextTick(()=>{const i=function(){const t=et();if(t)return rt[t]}(),s=o.call(t,e,n,r?i:null);s&&("function"==typeof s.then?s.then(t=>{lt(t,i)}).catch(t=>{}):lt(s,i))})}function st(){const t=et();t&&(rt[t]={x:window.pageXOffset,y:window.pageYOffset})}function at(t){st(),t.state&&t.state.key&&nt(t.state.key)}function ct(t){return ht(t.x)||ht(t.y)}function ut(t){return{x:ht(t.x)?t.x:window.pageXOffset,y:ht(t.y)?t.y:window.pageYOffset}}function ht(t){return"number"==typeof t}const pt=/^#\d/;function lt(t,e){const n="object"==typeof t;if(n&&"string"==typeof t.selector){const n=pt.test(t.selector)?document.getElementById(t.selector.slice(1)):document.querySelector(t.selector);if(n){let o=t.offset&&"object"==typeof t.offset?t.offset:{};e=function(t,e){const n=document.documentElement.getBoundingClientRect(),r=t.getBoundingClientRect();return{x:r.left-n.left-e.x,y:r.top-n.top-e.y}}(n,o={x:ht((r=o).x)?r.x:0,y:ht(r.y)?r.y:0})}else ct(t)&&(e=ut(t))}else n&&ct(t)&&(e=ut(t));var r;e&&window.scrollTo(e.x,e.y)}const ft=J&&function(){const t=window.navigator.userAgent;return(-1===t.indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&(window.history&&"function"==typeof window.history.pushState)}();function dt(t,e){st();const r=window.history;try{if(e){const e=n({},r.state);e.key=et(),r.replaceState(e,"",t)}else r.pushState({key:nt(Z())},"",t)}catch(n){window.location[e?"replace":"assign"](t)}}function yt(t){dt(t,!0)}function mt(t,e,n){const r=o=>{o>=t.length?n():t[o]?e(t[o],()=>{r(o+1)}):r(o+1)};r(0)}function gt(e){return(n,r,o)=>{let i=!1,s=0,a=null;wt(e,(e,n,r,c)=>{if("function"==typeof e&&void 0===e.cid){i=!0,s++;const n=xt(t=>{(function(t){return t.__esModule||bt&&"Module"===t[Symbol.toStringTag]})(t)&&(t=t.default),e.resolved="function"==typeof t?t:K.extend(t),r.components[c]=t,--s<=0&&o()}),u=xt(e=>{const n=`Failed to resolve async component ${c}: ${e}`;a||(a=t(e)?e:new Error(n),o(a))});let h;try{h=e(n,u)}catch(t){u(t)}if(h)if("function"==typeof h.then)h.then(n,u);else{const t=h.component;t&&"function"==typeof t.then&&t.then(n,u)}}}),i||o()}}function wt(t,e){return vt(t.map(t=>Object.keys(t.components).map(n=>e(t.components[n],t.instances[n],t,n))))}function vt(t){return Array.prototype.concat.apply([],t)}const bt="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function xt(t){let e=!1;return function(...n){if(!e)return e=!0,t.apply(this,n)}}const kt={redirected:1,aborted:2,cancelled:3,duplicated:4};function Rt(t,e){return Et(t,e,kt.redirected,`Redirected from "${t.fullPath}" to "${function(t){if("string"==typeof t)return t;if("path"in t)return t.path;const e={};return $t.forEach(n=>{n in t&&(e[n]=t[n])}),JSON.stringify(e,null,2)}(e)}" via a navigation guard.`)}function Et(t,e,n,r){const o=new Error(r);o._isRouter=!0,o.from=t,o.to=e,o.type=n;const i=o.stack.split("\n");return i.splice(1,2),o.stack=i.join("\n"),o}const $t=["params","query","hash"];class At{constructor(t,e){this.router=t,this.base=function(t){if(!t)if(J){const e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";"/"!==t.charAt(0)&&(t="/"+t);return t.replace(/\/$/,"")}(e),this.current=y,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]}listen(t){this.cb=t}onReady(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))}onError(t){this.errorCbs.push(t)}transitionTo(t,e,n){const r=this.router.match(t,this.current);this.confirmTransition(r,()=>{const t=this.current;this.updateRoute(r),e&&e(r),this.ensureURL(),this.router.afterHooks.forEach(e=>{e&&e(r,t)}),this.ready||(this.ready=!0,this.readyCbs.forEach(t=>{t(r)}))},t=>{n&&n(t),t&&!this.ready&&(this.ready=!0,this.readyErrorCbs.forEach(e=>{e(t)}))})}confirmTransition(n,r,o){const i=this.current,s=n=>{!e(n,kt.duplicated)&&t(n)&&(this.errorCbs.length?this.errorCbs.forEach(t=>{t(n)}):console.error(n)),o&&o(n)};if(w(n,i)&&n.matched.length===i.matched.length)return this.ensureURL(),s(Et(a=i,n,kt.duplicated,`Avoided redundant navigation to current location: "${a.fullPath}".`));var a;const{updated:c,deactivated:u,activated:h}=function(t,e){let n;const r=Math.max(t.length,e.length);for(n=0;nt.beforeEnter),gt(h));this.pending=n;const l=(e,r)=>{if(this.pending!==n)return s(function(t,e){return Et(t,e,kt.cancelled,`Navigation cancelled from "${t.fullPath}" to "${e.fullPath}" with a new navigation.`)}(i,n));try{e(n,i,e=>{!1===e?(this.ensureURL(!0),s(function(t,e){return Et(t,e,kt.aborted,`Navigation aborted from "${t.fullPath}" to "${e.fullPath}" via a navigation guard.`)}(i,n))):t(e)?(this.ensureURL(!0),s(e)):"string"==typeof e||"object"==typeof e&&("string"==typeof e.path||"string"==typeof e.name)?(s(Rt(i,n)),"object"==typeof e&&e.replace?this.replace(e):this.push(e)):r(e)})}catch(t){s(t)}};mt(p,l,()=>{const t=[];mt(function(t,e,n){return Ct(t,"beforeRouteEnter",(t,r,o,i)=>(function(t,e,n,r,o){return function(i,s,a){return t(i,s,t=>{"function"==typeof t&&r.push(()=>{!function t(e,n,r,o){n[r]&&!n[r]._isBeingDestroyed?e(n[r]):o()&&setTimeout(()=>{t(e,n,r,o)},16)}(t,e.instances,n,o)}),a(t)})}})(t,o,i,e,n))}(h,t,()=>this.current===n).concat(this.router.resolveHooks),l,()=>{if(this.pending!==n)return s();this.pending=null,r(n),this.router.app&&this.router.app.$nextTick(()=>{t.forEach(t=>{t()})})})})}updateRoute(t){this.current=t,this.cb&&this.cb(t)}setupListeners(){}teardownListeners(){this.listeners.forEach(t=>{t()}),this.listeners=[]}}function Ct(t,e,n,r){const o=wt(t,(t,r,o,i)=>{const s=function(t,e){"function"!=typeof t&&(t=K.extend(t));return t.options[e]}(t,e);if(s)return Array.isArray(s)?s.map(t=>n(t,r,o,i)):n(s,r,o,i)});return vt(r?o.reverse():o)}function Ot(t,e){if(e)return function(){return t.apply(e,arguments)}}class jt extends At{constructor(t,e){super(t,e),this._startLocation=St(this.base)}setupListeners(){if(this.listeners.length>0)return;const t=this.router,e=t.options.scrollBehavior,n=ft&&e;n&&this.listeners.push(ot());const r=()=>{const e=this.current,r=St(this.base);this.current===y&&r===this._startLocation||this.transitionTo(r,r=>{n&&it(t,r,e,!0)})};window.addEventListener("popstate",r),this.listeners.push(()=>{window.removeEventListener("popstate",r)})}go(t){window.history.go(t)}push(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{dt(x(this.base+t.fullPath)),it(this.router,t,r,!1),e&&e(t)},n)}replace(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{yt(x(this.base+t.fullPath)),it(this.router,t,r,!1),e&&e(t)},n)}ensureURL(t){if(St(this.base)!==this.current.fullPath){const e=x(this.base+this.current.fullPath);t?dt(e):yt(e)}}getCurrentLocation(){return St(this.base)}}function St(t){let e=decodeURI(window.location.pathname);return t&&0===e.toLowerCase().indexOf(t.toLowerCase())&&(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}class Lt extends At{constructor(t,e,n){super(t,e),n&&function(t){const e=St(t);if(!/^\/#/.test(e))return window.location.replace(x(t+"/#"+e)),!0}(this.base)||Tt()}setupListeners(){if(this.listeners.length>0)return;const t=this.router.options.scrollBehavior,e=ft&&t;e&&this.listeners.push(ot());const n=()=>{const t=this.current;Tt()&&this.transitionTo(_t(),n=>{e&&it(this.router,n,t,!0),ft||Ut(n.fullPath)})},r=ft?"popstate":"hashchange";window.addEventListener(r,n),this.listeners.push(()=>{window.removeEventListener(r,n)})}push(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{qt(t.fullPath),it(this.router,t,r,!1),e&&e(t)},n)}replace(t,e,n){const{current:r}=this;this.transitionTo(t,t=>{Ut(t.fullPath),it(this.router,t,r,!1),e&&e(t)},n)}go(t){window.history.go(t)}ensureURL(t){const e=this.current.fullPath;_t()!==e&&(t?qt(e):Ut(e))}getCurrentLocation(){return _t()}}function Tt(){const t=_t();return"/"===t.charAt(0)||(Ut("/"+t),!1)}function _t(){let t=window.location.href;const e=t.indexOf("#");if(e<0)return"";const n=(t=t.slice(e+1)).indexOf("?");if(n<0){const e=t.indexOf("#");t=e>-1?decodeURI(t.slice(0,e))+t.slice(e):decodeURI(t)}else t=decodeURI(t.slice(0,n))+t.slice(n);return t}function Pt(t){const e=window.location.href,n=e.indexOf("#");return`${n>=0?e.slice(0,n):e}#${t}`}function qt(t){ft?dt(Pt(t)):window.location.hash=t}function Ut(t){ft?yt(Pt(t)):window.location.replace(Pt(t))}class It extends At{constructor(t,e){super(t,e),this.stack=[],this.index=-1}push(t,e,n){this.transitionTo(t,t=>{this.stack=this.stack.slice(0,this.index+1).concat(t),this.index++,e&&e(t)},n)}replace(t,e,n){this.transitionTo(t,t=>{this.stack=this.stack.slice(0,this.index).concat(t),e&&e(t)},n)}go(t){const n=this.index+t;if(n<0||n>=this.stack.length)return;const r=this.stack[n];this.confirmTransition(r,()=>{this.index=n,this.updateRoute(r)},t=>{e(t,kt.duplicated)&&(this.index=n)})}getCurrentLocation(){const t=this.stack[this.stack.length-1];return t?t.fullPath:"/"}ensureURL(){}}class Mt{constructor(t={}){this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Y(t.routes||[],this);let e=t.mode||"hash";switch(this.fallback="history"===e&&!ft&&!1!==t.fallback,this.fallback&&(e="hash"),J||(e="abstract"),this.mode=e,e){case"history":this.history=new jt(this,t.base);break;case"hash":this.history=new Lt(this,t.base,this.fallback);break;case"abstract":this.history=new It(this,t.base)}}match(t,e,n){return this.matcher.match(t,e,n)}get currentRoute(){return this.history&&this.history.current}init(t){if(this.apps.push(t),t.$once("hook:destroyed",()=>{const e=this.apps.indexOf(t);e>-1&&this.apps.splice(e,1),this.app===t&&(this.app=this.apps[0]||null),this.app||this.history.teardownListeners()}),this.app)return;this.app=t;const e=this.history;if(e instanceof jt||e instanceof Lt){const t=()=>{e.setupListeners()};e.transitionTo(e.getCurrentLocation(),t,t)}e.listen(t=>{this.apps.forEach(e=>{e._route=t})})}beforeEach(t){return Vt(this.beforeHooks,t)}beforeResolve(t){return Vt(this.resolveHooks,t)}afterEach(t){return Vt(this.afterHooks,t)}onReady(t,e){this.history.onReady(t,e)}onError(t){this.history.onError(t)}push(t,e,n){if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((e,n)=>{this.history.push(t,e,n)});this.history.push(t,e,n)}replace(t,e,n){if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((e,n)=>{this.history.replace(t,e,n)});this.history.replace(t,e,n)}go(t){this.history.go(t)}back(){this.go(-1)}forward(){this.go(1)}getMatchedComponents(t){const e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map(t=>Object.keys(t.components).map(e=>t.components[e]))):[]}resolve(t,e,n){const r=B(t,e=e||this.history.current,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath;return{location:r,route:o,href:function(t,e,n){var r="hash"===n?"#"+e:e;return t?x(t+"/"+r):r}(this.history.base,i,this.mode),normalizedTo:r,resolved:o}}addRoutes(t){this.matcher.addRoutes(t),this.history.current!==y&&this.history.transitionTo(this.history.getCurrentLocation())}}function Vt(t,e){return t.push(e),()=>{const n=t.indexOf(e);n>-1&&t.splice(n,1)}}Mt.install=function t(e){if(t.installed&&K===e)return;t.installed=!0,K=e;const n=t=>void 0!==t,o=(t,e)=>{let r=t.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(t,e)};e.mixin({beforeCreate(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,o(this,this)},destroyed(){o(this)}}),Object.defineProperty(e.prototype,"$router",{get(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get(){return this._routerRoot._route}}),e.component("RouterView",r),e.component("RouterLink",N);const i=e.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created},Mt.version="3.3.0",J&&window.Vue&&window.Vue.use(Mt);export default Mt; \ No newline at end of file diff --git a/dist/vue-router.esm.js b/dist/vue-router.esm.js index c7937a04d..6ec69f9d4 100644 --- a/dist/vue-router.esm.js +++ b/dist/vue-router.esm.js @@ -1,5 +1,5 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ @@ -21,12 +21,8 @@ function isError (err) { return Object.prototype.toString.call(err).indexOf('Error') > -1 } -function isExtendedError (constructor, err) { - return ( - err instanceof constructor || - // _name is to support IE9 too - (err && (err.name === constructor.name || err._name === constructor._name)) - ) +function isRouterError (err, errorType) { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) } function extend (a, b) { @@ -1706,12 +1702,10 @@ function setupScroll () { var stateCopy = extend({}, window.history.state); stateCopy.key = getStateKey(); window.history.replaceState(stateCopy, '', absolutePath); - window.addEventListener('popstate', function (e) { - saveScrollPosition(); - if (e.state && e.state.key) { - setStateKey(e.state.key); - } - }); + window.addEventListener('popstate', handlePopState); + return function () { + window.removeEventListener('popstate', handlePopState); + } } function handleScroll ( @@ -1773,6 +1767,13 @@ function saveScrollPosition () { } } +function handlePopState (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } +} + function getScrollPosition () { var key = getStateKey(); if (key) { @@ -2012,32 +2013,73 @@ function once (fn) { } } -var NavigationDuplicated = /*@__PURE__*/(function (Error) { - function NavigationDuplicated (normalizedLocation) { - Error.call(this); - this.name = this._name = 'NavigationDuplicated'; - // passing the message to super() doesn't seem to work in the transpiled version - this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed"; - // add a stack property so services like Sentry can correctly display it - Object.defineProperty(this, 'stack', { - value: new Error().stack, - writable: true, - configurable: true - }); - // we could also have used - // Error.captureStackTrace(this, this.constructor) - // but it only exists on node and chrome - } +var NavigationFailureType = { + redirected: 1, + aborted: 2, + cancelled: 3, + duplicated: 4 +}; + +function createNavigationRedirectedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.redirected, + ("Redirected from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(to)) + "\" via a navigation guard.") + ) +} + +function createNavigationDuplicatedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.duplicated, + ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".") + ) +} + +function createNavigationCancelledError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.cancelled, + ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.") + ) +} + +function createNavigationAbortedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.aborted, + ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.") + ) +} + +function createRouterError (from, to, type, message) { + var error = new Error(message); + error._isRouter = true; + error.from = from; + error.to = to; + error.type = type; - if ( Error ) NavigationDuplicated.__proto__ = Error; - NavigationDuplicated.prototype = Object.create( Error && Error.prototype ); - NavigationDuplicated.prototype.constructor = NavigationDuplicated; + var newStack = error.stack.split('\n'); + newStack.splice(1, 2); // remove 2 last useless calls + error.stack = newStack.join('\n'); + return error +} - return NavigationDuplicated; -}(Error)); +var propertiesToLog = ['params', 'query', 'hash']; -// support IE9 -NavigationDuplicated._name = 'NavigationDuplicated'; +function stringifyRoute (to) { + if (typeof to === 'string') { return to } + if ('path' in to) { return to.path } + var location = {}; + propertiesToLog.forEach(function (key) { + if (key in to) { location[key] = to[key]; } + }); + return JSON.stringify(location, null, 2) +} /* */ @@ -2051,6 +2093,7 @@ var History = function History (router, base) { this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; + this.listeners = []; }; History.prototype.listen = function listen (cb) { @@ -2083,9 +2126,13 @@ History.prototype.transitionTo = function transitionTo ( this.confirmTransition( route, function () { + var prev = this$1.current; this$1.updateRoute(route); onComplete && onComplete(route); this$1.ensureURL(); + this$1.router.afterHooks.forEach(function (hook) { + hook && hook(route, prev); + }); // fire ready cbs once if (!this$1.ready) { @@ -2118,7 +2165,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.duplicated) && isError(err)) { if (this$1.errorCbs.length) { this$1.errorCbs.forEach(function (cb) { cb(err); @@ -2136,7 +2183,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl route.matched.length === current.matched.length ) { this.ensureURL(); - return abort(new NavigationDuplicated(route)) + return abort(createNavigationDuplicatedError(current, route)) } var ref = resolveQueue( @@ -2163,12 +2210,15 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { - return abort() + return abort(createNavigationCancelledError(current, route)) } try { hook(route, current, function (to) { - if (to === false || isError(to)) { + if (to === false) { // next(false) -> abort navigation, ensure current URL + this$1.ensureURL(true); + abort(createNavigationAbortedError(current, route)); + } else if (isError(to)) { this$1.ensureURL(true); abort(to); } else if ( @@ -2177,7 +2227,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect - abort(); + abort(createNavigationRedirectedError(current, route)); if (typeof to === 'object' && to.replace) { this$1.replace(to); } else { @@ -2218,12 +2268,19 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl }; History.prototype.updateRoute = function updateRoute (route) { - var prev = this.current; this.current = route; this.cb && this.cb(route); - this.router.afterHooks.forEach(function (hook) { - hook && hook(route, prev); +}; + +History.prototype.setupListeners = function setupListeners () { + // Default implementation is empty +}; + +History.prototype.teardownListeners = function teardownListeners () { + this.listeners.forEach(function (cleanupListener) { + cleanupListener(); }); + this.listeners = []; }; function normalizeBase (base) { @@ -2368,25 +2425,37 @@ function poll ( var HTML5History = /*@__PURE__*/(function (History) { function HTML5History (router, base) { + History.call(this, router, base); + + this._startLocation = getLocation(this.base); + } + + if ( History ) HTML5History.__proto__ = History; + HTML5History.prototype = Object.create( History && History.prototype ); + HTML5History.prototype.constructor = HTML5History; + + HTML5History.prototype.setupListeners = function setupListeners () { var this$1 = this; - History.call(this, router, base); + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - var initLocation = getLocation(this.base); - window.addEventListener('popstate', function (e) { + var handleRoutingEvent = function () { var current = this$1.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. var location = getLocation(this$1.base); - if (this$1.current === START && location === initLocation) { + if (this$1.current === START && location === this$1._startLocation) { return } @@ -2395,12 +2464,12 @@ var HTML5History = /*@__PURE__*/(function (History) { handleScroll(router, route, current, true); } }); + }; + window.addEventListener('popstate', handleRoutingEvent); + this.listeners.push(function () { + window.removeEventListener('popstate', handleRoutingEvent); }); - } - - if ( History ) HTML5History.__proto__ = History; - HTML5History.prototype = Object.create( History && History.prototype ); - HTML5History.prototype.constructor = HTML5History; + }; HTML5History.prototype.go = function go (n) { window.history.go(n); @@ -2473,31 +2542,40 @@ var HashHistory = /*@__PURE__*/(function (History) { HashHistory.prototype.setupListeners = function setupListeners () { var this$1 = this; + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - function () { - var current = this$1.current; - if (!ensureSlash()) { - return - } - this$1.transitionTo(getHash(), function (route) { - if (supportsScroll) { - handleScroll(this$1.router, route, current, true); - } - if (!supportsPushState) { - replaceHash(route.fullPath); - } - }); + var handleRoutingEvent = function () { + var current = this$1.current; + if (!ensureSlash()) { + return } + this$1.transitionTo(getHash(), function (route) { + if (supportsScroll) { + handleScroll(this$1.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + }; + var eventType = supportsPushState ? 'popstate' : 'hashchange'; + window.addEventListener( + eventType, + handleRoutingEvent ); + this.listeners.push(function () { + window.removeEventListener(eventType, handleRoutingEvent); + }); }; HashHistory.prototype.push = function push (location, onComplete, onAbort) { @@ -2670,7 +2748,7 @@ var AbstractHistory = /*@__PURE__*/(function (History) { this$1.updateRoute(route); }, function (err) { - if (isExtendedError(NavigationDuplicated, err)) { + if (isRouterError(err, NavigationFailureType.duplicated)) { this$1.index = targetIndex; } } @@ -2765,6 +2843,12 @@ VueRouter.prototype.init = function init (app /* Vue component instance */) { // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } + + if (!this$1.app) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this$1.history.teardownListeners(); + } }); // main app previously initialized @@ -2777,17 +2861,11 @@ VueRouter.prototype.init = function init (app /* Vue component instance */) { var history = this.history; - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()); - } else if (history instanceof HashHistory) { - var setupHashListener = function () { + if (history instanceof HTML5History || history instanceof HashHistory) { + var setupListeners = function () { history.setupListeners(); }; - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ); + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners); } history.listen(function (route) { @@ -2920,7 +2998,7 @@ function createHref (base, fullPath, mode) { } VueRouter.install = install; -VueRouter.version = '3.2.0'; +VueRouter.version = '3.3.0'; if (inBrowser && window.Vue) { window.Vue.use(VueRouter); diff --git a/dist/vue-router.js b/dist/vue-router.js index f5532aaca..9c1c5b65b 100644 --- a/dist/vue-router.js +++ b/dist/vue-router.js @@ -1,5 +1,5 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ @@ -27,12 +27,8 @@ return Object.prototype.toString.call(err).indexOf('Error') > -1 } - function isExtendedError (constructor, err) { - return ( - err instanceof constructor || - // _name is to support IE9 too - (err && (err.name === constructor.name || err._name === constructor._name)) - ) + function isRouterError (err, errorType) { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) } function extend (a, b) { @@ -1712,12 +1708,10 @@ var stateCopy = extend({}, window.history.state); stateCopy.key = getStateKey(); window.history.replaceState(stateCopy, '', absolutePath); - window.addEventListener('popstate', function (e) { - saveScrollPosition(); - if (e.state && e.state.key) { - setStateKey(e.state.key); - } - }); + window.addEventListener('popstate', handlePopState); + return function () { + window.removeEventListener('popstate', handlePopState); + } } function handleScroll ( @@ -1779,6 +1773,13 @@ } } + function handlePopState (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } + } + function getScrollPosition () { var key = getStateKey(); if (key) { @@ -2018,32 +2019,73 @@ } } - var NavigationDuplicated = /*@__PURE__*/(function (Error) { - function NavigationDuplicated (normalizedLocation) { - Error.call(this); - this.name = this._name = 'NavigationDuplicated'; - // passing the message to super() doesn't seem to work in the transpiled version - this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed"; - // add a stack property so services like Sentry can correctly display it - Object.defineProperty(this, 'stack', { - value: new Error().stack, - writable: true, - configurable: true - }); - // we could also have used - // Error.captureStackTrace(this, this.constructor) - // but it only exists on node and chrome - } + var NavigationFailureType = { + redirected: 1, + aborted: 2, + cancelled: 3, + duplicated: 4 + }; + + function createNavigationRedirectedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.redirected, + ("Redirected from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(to)) + "\" via a navigation guard.") + ) + } + + function createNavigationDuplicatedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.duplicated, + ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".") + ) + } + + function createNavigationCancelledError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.cancelled, + ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.") + ) + } + + function createNavigationAbortedError (from, to) { + return createRouterError( + from, + to, + NavigationFailureType.aborted, + ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.") + ) + } + + function createRouterError (from, to, type, message) { + var error = new Error(message); + error._isRouter = true; + error.from = from; + error.to = to; + error.type = type; - if ( Error ) NavigationDuplicated.__proto__ = Error; - NavigationDuplicated.prototype = Object.create( Error && Error.prototype ); - NavigationDuplicated.prototype.constructor = NavigationDuplicated; + var newStack = error.stack.split('\n'); + newStack.splice(1, 2); // remove 2 last useless calls + error.stack = newStack.join('\n'); + return error + } - return NavigationDuplicated; - }(Error)); + var propertiesToLog = ['params', 'query', 'hash']; - // support IE9 - NavigationDuplicated._name = 'NavigationDuplicated'; + function stringifyRoute (to) { + if (typeof to === 'string') { return to } + if ('path' in to) { return to.path } + var location = {}; + propertiesToLog.forEach(function (key) { + if (key in to) { location[key] = to[key]; } + }); + return JSON.stringify(location, null, 2) + } /* */ @@ -2057,6 +2099,7 @@ this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; + this.listeners = []; }; History.prototype.listen = function listen (cb) { @@ -2089,9 +2132,13 @@ this.confirmTransition( route, function () { + var prev = this$1.current; this$1.updateRoute(route); onComplete && onComplete(route); this$1.ensureURL(); + this$1.router.afterHooks.forEach(function (hook) { + hook && hook(route, prev); + }); // fire ready cbs once if (!this$1.ready) { @@ -2124,7 +2171,7 @@ // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError - if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (!isRouterError(err, NavigationFailureType.duplicated) && isError(err)) { if (this$1.errorCbs.length) { this$1.errorCbs.forEach(function (cb) { cb(err); @@ -2142,7 +2189,7 @@ route.matched.length === current.matched.length ) { this.ensureURL(); - return abort(new NavigationDuplicated(route)) + return abort(createNavigationDuplicatedError(current, route)) } var ref = resolveQueue( @@ -2169,12 +2216,15 @@ this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { - return abort() + return abort(createNavigationCancelledError(current, route)) } try { hook(route, current, function (to) { - if (to === false || isError(to)) { + if (to === false) { // next(false) -> abort navigation, ensure current URL + this$1.ensureURL(true); + abort(createNavigationAbortedError(current, route)); + } else if (isError(to)) { this$1.ensureURL(true); abort(to); } else if ( @@ -2183,7 +2233,7 @@ (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect - abort(); + abort(createNavigationRedirectedError(current, route)); if (typeof to === 'object' && to.replace) { this$1.replace(to); } else { @@ -2224,12 +2274,19 @@ }; History.prototype.updateRoute = function updateRoute (route) { - var prev = this.current; this.current = route; this.cb && this.cb(route); - this.router.afterHooks.forEach(function (hook) { - hook && hook(route, prev); + }; + + History.prototype.setupListeners = function setupListeners () { + // Default implementation is empty + }; + + History.prototype.teardownListeners = function teardownListeners () { + this.listeners.forEach(function (cleanupListener) { + cleanupListener(); }); + this.listeners = []; }; function normalizeBase (base) { @@ -2374,25 +2431,37 @@ var HTML5History = /*@__PURE__*/(function (History) { function HTML5History (router, base) { + History.call(this, router, base); + + this._startLocation = getLocation(this.base); + } + + if ( History ) HTML5History.__proto__ = History; + HTML5History.prototype = Object.create( History && History.prototype ); + HTML5History.prototype.constructor = HTML5History; + + HTML5History.prototype.setupListeners = function setupListeners () { var this$1 = this; - History.call(this, router, base); + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - var initLocation = getLocation(this.base); - window.addEventListener('popstate', function (e) { + var handleRoutingEvent = function () { var current = this$1.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. var location = getLocation(this$1.base); - if (this$1.current === START && location === initLocation) { + if (this$1.current === START && location === this$1._startLocation) { return } @@ -2401,12 +2470,12 @@ handleScroll(router, route, current, true); } }); + }; + window.addEventListener('popstate', handleRoutingEvent); + this.listeners.push(function () { + window.removeEventListener('popstate', handleRoutingEvent); }); - } - - if ( History ) HTML5History.__proto__ = History; - HTML5History.prototype = Object.create( History && History.prototype ); - HTML5History.prototype.constructor = HTML5History; + }; HTML5History.prototype.go = function go (n) { window.history.go(n); @@ -2479,31 +2548,40 @@ HashHistory.prototype.setupListeners = function setupListeners () { var this$1 = this; + if (this.listeners.length > 0) { + return + } + var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { - setupScroll(); + this.listeners.push(setupScroll()); } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - function () { - var current = this$1.current; - if (!ensureSlash()) { - return - } - this$1.transitionTo(getHash(), function (route) { - if (supportsScroll) { - handleScroll(this$1.router, route, current, true); - } - if (!supportsPushState) { - replaceHash(route.fullPath); - } - }); + var handleRoutingEvent = function () { + var current = this$1.current; + if (!ensureSlash()) { + return } + this$1.transitionTo(getHash(), function (route) { + if (supportsScroll) { + handleScroll(this$1.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + }; + var eventType = supportsPushState ? 'popstate' : 'hashchange'; + window.addEventListener( + eventType, + handleRoutingEvent ); + this.listeners.push(function () { + window.removeEventListener(eventType, handleRoutingEvent); + }); }; HashHistory.prototype.push = function push (location, onComplete, onAbort) { @@ -2676,7 +2754,7 @@ this$1.updateRoute(route); }, function (err) { - if (isExtendedError(NavigationDuplicated, err)) { + if (isRouterError(err, NavigationFailureType.duplicated)) { this$1.index = targetIndex; } } @@ -2771,6 +2849,12 @@ // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } + + if (!this$1.app) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this$1.history.teardownListeners(); + } }); // main app previously initialized @@ -2783,17 +2867,11 @@ var history = this.history; - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()); - } else if (history instanceof HashHistory) { - var setupHashListener = function () { + if (history instanceof HTML5History || history instanceof HashHistory) { + var setupListeners = function () { history.setupListeners(); }; - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ); + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners); } history.listen(function (route) { @@ -2926,7 +3004,7 @@ } VueRouter.install = install; - VueRouter.version = '3.2.0'; + VueRouter.version = '3.3.0'; if (inBrowser && window.Vue) { window.Vue.use(VueRouter); diff --git a/dist/vue-router.min.js b/dist/vue-router.min.js index c2986691b..8cfffe57d 100644 --- a/dist/vue-router.min.js +++ b/dist/vue-router.min.js @@ -1,6 +1,6 @@ /*! - * vue-router v3.2.0 + * vue-router v3.3.0 * (c) 2020 Evan You * @license MIT */ -var t,e;t=this,e=function(){"use strict";function t(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function e(t,e){return e instanceof t||e&&(e.name===t.name||e._name===t._name)}function r(t,e){for(var r in e)t[r]=e[r];return t}var n={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(t,e){var n=e.props,i=e.children,a=e.parent,c=e.data;c.routerView=!0;for(var u=a.$createElement,s=n.name,p=a.$route,f=a._routerViewCache||(a._routerViewCache={}),h=0,l=!1;a&&a._routerRoot!==a;){var d=a.$vnode?a.$vnode.data:{};d.routerView&&h++,d.keepAlive&&a._directInactive&&a._inactive&&(l=!0),a=a.$parent}if(c.routerViewDepth=h,l){var v=f[s],y=v&&v.component;return y?(v.configProps&&o(y,c,v.route,v.configProps),u(y,c,i)):u()}var m=p.matched[h],g=m&&m.components[s];if(!m||!g)return f[s]=null,u();f[s]={component:g},c.registerRouteInstance=function(t,e){var r=m.instances[s];(e&&r!==t||!e&&r===t)&&(m.instances[s]=e)},(c.hook||(c.hook={})).prepatch=function(t,e){m.instances[s]=e.componentInstance},c.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==m.instances[s]&&(m.instances[s]=t.componentInstance)};var w=m.props&&m.props[s];return w&&(r(f[s],{route:p,configProps:w}),o(g,c,p,w)),u(g,c,i)}};function o(t,e,n,o){var i=e.props=function(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0}}(n,o);if(i){i=e.props=r({},i);var a=e.attrs=e.attrs||{};for(var c in i)t.props&&c in t.props||(a[c]=i[c],delete i[c])}}var i=/[!'()*]/g,a=function(t){return"%"+t.charCodeAt(0).toString(16)},c=/%2C/g,u=function(t){return encodeURIComponent(t).replace(i,a).replace(c,",")},s=decodeURIComponent;function p(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach(function(t){var r=t.replace(/\+/g," ").split("="),n=s(r.shift()),o=r.length>0?s(r.join("=")):null;void 0===e[n]?e[n]=o:Array.isArray(e[n])?e[n].push(o):e[n]=[e[n],o]}),e):e}function f(t){var e=t?Object.keys(t).map(function(e){var r=t[e];if(void 0===r)return"";if(null===r)return u(e);if(Array.isArray(r)){var n=[];return r.forEach(function(t){void 0!==t&&(null===t?n.push(u(e)):n.push(u(e)+"="+u(t)))}),n.join("&")}return u(e)+"="+u(r)}).filter(function(t){return t.length>0}).join("&"):null;return e?"?"+e:""}var h=/\/?$/;function l(t,e,r,n){var o=n&&n.options.stringifyQuery,i=e.query||{};try{i=d(i)}catch(t){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:m(e,o),matched:t?y(t):[]};return r&&(a.redirectedFrom=m(r,o)),Object.freeze(a)}function d(t){if(Array.isArray(t))return t.map(d);if(t&&"object"==typeof t){var e={};for(var r in t)e[r]=d(t[r]);return e}return t}var v=l(null,{path:"/"});function y(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function m(t,e){var r=t.path,n=t.query;void 0===n&&(n={});var o=t.hash;return void 0===o&&(o=""),(r||"/")+(e||f)(n)+o}function g(t,e){return e===v?t===e:!!e&&(t.path&&e.path?t.path.replace(h,"")===e.path.replace(h,"")&&t.hash===e.hash&&w(t.query,e.query):!(!t.name||!e.name)&&t.name===e.name&&t.hash===e.hash&&w(t.query,e.query)&&w(t.params,e.params))}function w(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var r=Object.keys(t),n=Object.keys(e);return r.length===n.length&&r.every(function(r){var n=t[r],o=e[r];return"object"==typeof n&&"object"==typeof o?w(n,o):String(n)===String(o)})}function b(t,e,r){var n=t.charAt(0);if("/"===n)return t;if("?"===n||"#"===n)return e+t;var o=e.split("/");r&&o[o.length-1]||o.pop();for(var i=t.replace(/^\//,"").split("/"),a=0;a=0&&(e=t.slice(n),t=t.slice(0,n));var o=t.indexOf("?");return o>=0&&(r=t.slice(o+1),t=t.slice(0,o)),{path:t,query:r,hash:e}}(i.path||""),f=e&&e.path||"/",h=s.path?b(s.path,f,n||i.append):f,l=function(t,e,r){void 0===e&&(e={});var n,o=r||p;try{n=o(t||"")}catch(t){n={}}for(var i in e)n[i]=e[i];return n}(s.query,i.query,o&&o.options.parseQuery),d=i.hash||s.hash;return d&&"#"!==d.charAt(0)&&(d="#"+d),{_normalized:!0,path:h,query:l,hash:d}}var H,z=[String,Object],D=[String,Array],F=function(){},N={name:"RouterLink",props:{to:{type:z,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:D,default:"click"}},render:function(t){var e=this,n=this.$router,o=this.$route,i=n.resolve(this.to,o,this.append),a=i.location,c=i.route,u=i.href,s={},p=n.options.linkActiveClass,f=n.options.linkExactActiveClass,d=null==p?"router-link-active":p,v=null==f?"router-link-exact-active":f,y=null==this.activeClass?d:this.activeClass,m=null==this.exactActiveClass?v:this.exactActiveClass,w=c.redirectedFrom?l(null,B(c.redirectedFrom),null,n):c;s[m]=g(o,w),s[y]=this.exact?s[m]:function(t,e){return 0===t.path.replace(h,"/").indexOf(e.path.replace(h,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var r in e)if(!(r in t))return!1;return!0}(t.query,e.query)}(o,w);var b=s[m]?this.ariaCurrentValue:null,x=function(t){K(t)&&(e.replace?n.replace(a,F):n.push(a,F))},k={click:K};Array.isArray(this.event)?this.event.forEach(function(t){k[t]=x}):k[this.event]=x;var R={class:s},E=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:u,route:c,navigate:x,isActive:s[y],isExactActive:s[m]});if(E){if(1===E.length)return E[0];if(E.length>1||!E.length)return 0===E.length?t():t("span",{},E)}if("a"===this.tag)R.on=k,R.attrs={href:u,"aria-current":b};else{var O=function t(e){if(e)for(var r,n=0;n-1&&(c.params[h]=r.params[h]);return c.path=V(p.path,c.params),u(p,c,a)}if(c.path){c.params={};for(var l=0;l=t.length?r():t[o]?e(t[o],function(){n(o+1)}):n(o+1)};n(0)}function mt(e){return function(r,n,o){var i=!1,a=0,c=null;gt(e,function(e,r,n,u){if("function"==typeof e&&void 0===e.cid){i=!0,a++;var s,p=xt(function(t){var r;((r=t).__esModule||bt&&"Module"===r[Symbol.toStringTag])&&(t=t.default),e.resolved="function"==typeof t?t:H.extend(t),n.components[u]=t,--a<=0&&o()}),f=xt(function(e){var r="Failed to resolve async component "+u+": "+e;c||(c=t(e)?e:new Error(r),o(c))});try{s=e(p,f)}catch(t){f(t)}if(s)if("function"==typeof s.then)s.then(p,f);else{var h=s.component;h&&"function"==typeof h.then&&h.then(p,f)}}}),i||o()}}function gt(t,e){return wt(t.map(function(t){return Object.keys(t.components).map(function(r){return e(t.components[r],t.instances[r],t,r)})}))}function wt(t){return Array.prototype.concat.apply([],t)}var bt="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function xt(t){var e=!1;return function(){for(var r=[],n=arguments.length;n--;)r[n]=arguments[n];if(!e)return e=!0,t.apply(this,r)}}var kt=function(t){function e(e){t.call(this),this.name=this._name="NavigationDuplicated",this.message='Navigating to current location ("'+e.fullPath+'") is not allowed',Object.defineProperty(this,"stack",{value:(new t).stack,writable:!0,configurable:!0})}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(Error);kt._name="NavigationDuplicated";var Rt=function(t,e){this.router=t,this.base=function(t){if(!t)if(J){var e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}(e),this.current=v,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function Et(t,e,r,n){var o=gt(t,function(t,n,o,i){var a=function(t,e){return"function"!=typeof t&&(t=H.extend(t)),t.options[e]}(t,e);if(a)return Array.isArray(a)?a.map(function(t){return r(t,n,o,i)}):r(a,n,o,i)});return wt(n?o.reverse():o)}function Ot(t,e){if(e)return function(){return t.apply(e,arguments)}}Rt.prototype.listen=function(t){this.cb=t},Rt.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},Rt.prototype.onError=function(t){this.errorCbs.push(t)},Rt.prototype.transitionTo=function(t,e,r){var n=this,o=this.router.match(t,this.current);this.confirmTransition(o,function(){n.updateRoute(o),e&&e(o),n.ensureURL(),n.ready||(n.ready=!0,n.readyCbs.forEach(function(t){t(o)}))},function(t){r&&r(t),t&&!n.ready&&(n.ready=!0,n.readyErrorCbs.forEach(function(e){e(t)}))})},Rt.prototype.confirmTransition=function(r,n,o){var i=this,a=this.current,c=function(r){!e(kt,r)&&t(r)&&(i.errorCbs.length?i.errorCbs.forEach(function(t){t(r)}):console.error(r)),o&&o(r)};if(g(r,a)&&r.matched.length===a.matched.length)return this.ensureURL(),c(new kt(r));var u=function(t,e){var r,n=Math.max(t.length,e.length);for(r=0;r-1?decodeURI(t.slice(0,n))+t.slice(n):decodeURI(t)}else t=decodeURI(t.slice(0,r))+t.slice(r);return t}function $t(t){var e=window.location.href,r=e.indexOf("#");return(r>=0?e.slice(0,r):e)+"#"+t}function Tt(t){lt?dt($t(t)):window.location.hash=t}function Pt(t){lt?vt($t(t)):window.location.replace($t(t))}var Lt=function(t){function r(e,r){t.call(this,e,r),this.stack=[],this.index=-1}return t&&(r.__proto__=t),r.prototype=Object.create(t&&t.prototype),r.prototype.constructor=r,r.prototype.push=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index+1).concat(t),n.index++,e&&e(t)},r)},r.prototype.replace=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index).concat(t),e&&e(t)},r)},r.prototype.go=function(t){var r=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var o=this.stack[n];this.confirmTransition(o,function(){r.index=n,r.updateRoute(o)},function(t){e(kt,t)&&(r.index=n)})}},r.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},r.prototype.ensureURL=function(){},r}(Rt),qt=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Y(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!lt&&!1!==t.fallback,this.fallback&&(e="hash"),J||(e="abstract"),this.mode=e,e){case"history":this.history=new _t(this,t.base);break;case"hash":this.history=new Ct(this,t.base,this.fallback);break;case"abstract":this.history=new Lt(this,t.base)}},Ut={currentRoute:{configurable:!0}};function It(t,e){return t.push(e),function(){var r=t.indexOf(e);r>-1&&t.splice(r,1)}}return qt.prototype.match=function(t,e,r){return this.matcher.match(t,e,r)},Ut.currentRoute.get=function(){return this.history&&this.history.current},qt.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",function(){var r=e.apps.indexOf(t);r>-1&&e.apps.splice(r,1),e.app===t&&(e.app=e.apps[0]||null)}),!this.app){this.app=t;var r=this.history;if(r instanceof _t)r.transitionTo(r.getCurrentLocation());else if(r instanceof Ct){var n=function(){r.setupListeners()};r.transitionTo(r.getCurrentLocation(),n,n)}r.listen(function(t){e.apps.forEach(function(e){e._route=t})})}},qt.prototype.beforeEach=function(t){return It(this.beforeHooks,t)},qt.prototype.beforeResolve=function(t){return It(this.resolveHooks,t)},qt.prototype.afterEach=function(t){return It(this.afterHooks,t)},qt.prototype.onReady=function(t,e){this.history.onReady(t,e)},qt.prototype.onError=function(t){this.history.onError(t)},qt.prototype.push=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.push(t,e,r)});this.history.push(t,e,r)},qt.prototype.replace=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.replace(t,e,r)});this.history.replace(t,e,r)},qt.prototype.go=function(t){this.history.go(t)},qt.prototype.back=function(){this.go(-1)},qt.prototype.forward=function(){this.go(1)},qt.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map(function(t){return Object.keys(t.components).map(function(e){return t.components[e]})})):[]},qt.prototype.resolve=function(t,e,r){var n=B(t,e=e||this.history.current,r,this),o=this.match(n,e),i=o.redirectedFrom||o.fullPath;return{location:n,route:o,href:function(t,e,r){var n="hash"===r?"#"+e:e;return t?x(t+"/"+n):n}(this.history.base,i,this.mode),normalizedTo:n,resolved:o}},qt.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==v&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(qt.prototype,Ut),qt.install=function t(e){if(!t.installed||H!==e){t.installed=!0,H=e;var r=function(t){return void 0!==t},o=function(t,e){var n=t.$options._parentVnode;r(n)&&r(n=n.data)&&r(n=n.registerRouteInstance)&&n(t,e)};e.mixin({beforeCreate:function(){r(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,o(this,this)},destroyed:function(){o(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",n),e.component("RouterLink",N);var i=e.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created}},qt.version="3.2.0",J&&window.Vue&&window.Vue.use(qt),qt},"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).VueRouter=e(); \ No newline at end of file +var t,e;t=this,e=function(){"use strict";function t(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function e(e,r){return t(e)&&e._isRouter&&(null==r||e.type===r)}function r(t,e){for(var r in e)t[r]=e[r];return t}var n={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(t,e){var n=e.props,i=e.children,a=e.parent,s=e.data;s.routerView=!0;for(var u=a.$createElement,c=n.name,p=a.$route,f=a._routerViewCache||(a._routerViewCache={}),h=0,l=!1;a&&a._routerRoot!==a;){var d=a.$vnode?a.$vnode.data:{};d.routerView&&h++,d.keepAlive&&a._directInactive&&a._inactive&&(l=!0),a=a.$parent}if(s.routerViewDepth=h,l){var v=f[c],y=v&&v.component;return y?(v.configProps&&o(y,s,v.route,v.configProps),u(y,s,i)):u()}var m=p.matched[h],g=m&&m.components[c];if(!m||!g)return f[c]=null,u();f[c]={component:g},s.registerRouteInstance=function(t,e){var r=m.instances[c];(e&&r!==t||!e&&r===t)&&(m.instances[c]=e)},(s.hook||(s.hook={})).prepatch=function(t,e){m.instances[c]=e.componentInstance},s.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==m.instances[c]&&(m.instances[c]=t.componentInstance)};var w=m.props&&m.props[c];return w&&(r(f[c],{route:p,configProps:w}),o(g,s,p,w)),u(g,s,i)}};function o(t,e,n,o){var i=e.props=function(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0}}(n,o);if(i){i=e.props=r({},i);var a=e.attrs=e.attrs||{};for(var s in i)t.props&&s in t.props||(a[s]=i[s],delete i[s])}}var i=/[!'()*]/g,a=function(t){return"%"+t.charCodeAt(0).toString(16)},s=/%2C/g,u=function(t){return encodeURIComponent(t).replace(i,a).replace(s,",")},c=decodeURIComponent;function p(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach(function(t){var r=t.replace(/\+/g," ").split("="),n=c(r.shift()),o=r.length>0?c(r.join("=")):null;void 0===e[n]?e[n]=o:Array.isArray(e[n])?e[n].push(o):e[n]=[e[n],o]}),e):e}function f(t){var e=t?Object.keys(t).map(function(e){var r=t[e];if(void 0===r)return"";if(null===r)return u(e);if(Array.isArray(r)){var n=[];return r.forEach(function(t){void 0!==t&&(null===t?n.push(u(e)):n.push(u(e)+"="+u(t)))}),n.join("&")}return u(e)+"="+u(r)}).filter(function(t){return t.length>0}).join("&"):null;return e?"?"+e:""}var h=/\/?$/;function l(t,e,r,n){var o=n&&n.options.stringifyQuery,i=e.query||{};try{i=d(i)}catch(t){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:m(e,o),matched:t?y(t):[]};return r&&(a.redirectedFrom=m(r,o)),Object.freeze(a)}function d(t){if(Array.isArray(t))return t.map(d);if(t&&"object"==typeof t){var e={};for(var r in t)e[r]=d(t[r]);return e}return t}var v=l(null,{path:"/"});function y(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function m(t,e){var r=t.path,n=t.query;void 0===n&&(n={});var o=t.hash;return void 0===o&&(o=""),(r||"/")+(e||f)(n)+o}function g(t,e){return e===v?t===e:!!e&&(t.path&&e.path?t.path.replace(h,"")===e.path.replace(h,"")&&t.hash===e.hash&&w(t.query,e.query):!(!t.name||!e.name)&&t.name===e.name&&t.hash===e.hash&&w(t.query,e.query)&&w(t.params,e.params))}function w(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var r=Object.keys(t),n=Object.keys(e);return r.length===n.length&&r.every(function(r){var n=t[r],o=e[r];return"object"==typeof n&&"object"==typeof o?w(n,o):String(n)===String(o)})}function b(t,e,r){var n=t.charAt(0);if("/"===n)return t;if("?"===n||"#"===n)return e+t;var o=e.split("/");r&&o[o.length-1]||o.pop();for(var i=t.replace(/^\//,"").split("/"),a=0;a=0&&(e=t.slice(n),t=t.slice(0,n));var o=t.indexOf("?");return o>=0&&(r=t.slice(o+1),t=t.slice(0,o)),{path:t,query:r,hash:e}}(i.path||""),f=e&&e.path||"/",h=c.path?b(c.path,f,n||i.append):f,l=function(t,e,r){void 0===e&&(e={});var n,o=r||p;try{n=o(t||"")}catch(t){n={}}for(var i in e)n[i]=e[i];return n}(c.query,i.query,o&&o.options.parseQuery),d=i.hash||c.hash;return d&&"#"!==d.charAt(0)&&(d="#"+d),{_normalized:!0,path:h,query:l,hash:d}}var H,z=[String,Object],F=[String,Array],N=function(){},D={name:"RouterLink",props:{to:{type:z,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:F,default:"click"}},render:function(t){var e=this,n=this.$router,o=this.$route,i=n.resolve(this.to,o,this.append),a=i.location,s=i.route,u=i.href,c={},p=n.options.linkActiveClass,f=n.options.linkExactActiveClass,d=null==p?"router-link-active":p,v=null==f?"router-link-exact-active":f,y=null==this.activeClass?d:this.activeClass,m=null==this.exactActiveClass?v:this.exactActiveClass,w=s.redirectedFrom?l(null,B(s.redirectedFrom),null,n):s;c[m]=g(o,w),c[y]=this.exact?c[m]:function(t,e){return 0===t.path.replace(h,"/").indexOf(e.path.replace(h,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var r in e)if(!(r in t))return!1;return!0}(t.query,e.query)}(o,w);var b=c[m]?this.ariaCurrentValue:null,x=function(t){K(t)&&(e.replace?n.replace(a,N):n.push(a,N))},R={click:K};Array.isArray(this.event)?this.event.forEach(function(t){R[t]=x}):R[this.event]=x;var k={class:c},E=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:u,route:s,navigate:x,isActive:c[y],isExactActive:c[m]});if(E){if(1===E.length)return E[0];if(E.length>1||!E.length)return 0===E.length?t():t("span",{},E)}if("a"===this.tag)k.on=R,k.attrs={href:u,"aria-current":b};else{var O=function t(e){if(e)for(var r,n=0;n-1&&(s.params[h]=r.params[h]);return s.path=V(p.path,s.params),u(p,s,a)}if(s.path){s.params={};for(var l=0;l=t.length?r():t[o]?e(t[o],function(){n(o+1)}):n(o+1)};n(0)}function gt(e){return function(r,n,o){var i=!1,a=0,s=null;wt(e,function(e,r,n,u){if("function"==typeof e&&void 0===e.cid){i=!0,a++;var c,p=Rt(function(t){var r;((r=t).__esModule||xt&&"Module"===r[Symbol.toStringTag])&&(t=t.default),e.resolved="function"==typeof t?t:H.extend(t),n.components[u]=t,--a<=0&&o()}),f=Rt(function(e){var r="Failed to resolve async component "+u+": "+e;s||(s=t(e)?e:new Error(r),o(s))});try{c=e(p,f)}catch(t){f(t)}if(c)if("function"==typeof c.then)c.then(p,f);else{var h=c.component;h&&"function"==typeof h.then&&h.then(p,f)}}}),i||o()}}function wt(t,e){return bt(t.map(function(t){return Object.keys(t.components).map(function(r){return e(t.components[r],t.instances[r],t,r)})}))}function bt(t){return Array.prototype.concat.apply([],t)}var xt="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function Rt(t){var e=!1;return function(){for(var r=[],n=arguments.length;n--;)r[n]=arguments[n];if(!e)return e=!0,t.apply(this,r)}}var kt={redirected:1,aborted:2,cancelled:3,duplicated:4};function Et(t,e){return Ot(t,e,kt.redirected,'Redirected from "'+t.fullPath+'" to "'+function(t){if("string"==typeof t)return t;if("path"in t)return t.path;var e={};return At.forEach(function(r){r in t&&(e[r]=t[r])}),JSON.stringify(e,null,2)}(e)+'" via a navigation guard.')}function Ot(t,e,r,n){var o=new Error(n);o._isRouter=!0,o.from=t,o.to=e,o.type=r;var i=o.stack.split("\n");return i.splice(1,2),o.stack=i.join("\n"),o}var At=["params","query","hash"],Ct=function(t,e){this.router=t,this.base=function(t){if(!t)if(J){var e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}(e),this.current=v,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function _t(t,e,r,n){var o=wt(t,function(t,n,o,i){var a=function(t,e){return"function"!=typeof t&&(t=H.extend(t)),t.options[e]}(t,e);if(a)return Array.isArray(a)?a.map(function(t){return r(t,n,o,i)}):r(a,n,o,i)});return bt(n?o.reverse():o)}function jt(t,e){if(e)return function(){return t.apply(e,arguments)}}Ct.prototype.listen=function(t){this.cb=t},Ct.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},Ct.prototype.onError=function(t){this.errorCbs.push(t)},Ct.prototype.transitionTo=function(t,e,r){var n=this,o=this.router.match(t,this.current);this.confirmTransition(o,function(){var t=n.current;n.updateRoute(o),e&&e(o),n.ensureURL(),n.router.afterHooks.forEach(function(e){e&&e(o,t)}),n.ready||(n.ready=!0,n.readyCbs.forEach(function(t){t(o)}))},function(t){r&&r(t),t&&!n.ready&&(n.ready=!0,n.readyErrorCbs.forEach(function(e){e(t)}))})},Ct.prototype.confirmTransition=function(r,n,o){var i,a=this,s=this.current,u=function(r){!e(r,kt.duplicated)&&t(r)&&(a.errorCbs.length?a.errorCbs.forEach(function(t){t(r)}):console.error(r)),o&&o(r)};if(g(r,s)&&r.matched.length===s.matched.length)return this.ensureURL(),u(Ot(i=s,r,kt.duplicated,'Avoided redundant navigation to current location: "'+i.fullPath+'".'));var c=function(t,e){var r,n=Math.max(t.length,e.length);for(r=0;r0)){var e=this.router,r=e.options.scrollBehavior,n=dt&&r;n&&this.listeners.push(ot());var o=function(){var r=t.current,o=Lt(t.base);t.current===v&&o===t._startLocation||t.transitionTo(o,function(t){n&&it(e,t,r,!0)})};window.addEventListener("popstate",o),this.listeners.push(function(){window.removeEventListener("popstate",o)})}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){vt(x(n.base+t.fullPath)),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.replace=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){yt(x(n.base+t.fullPath)),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.ensureURL=function(t){if(Lt(this.base)!==this.current.fullPath){var e=x(this.base+this.current.fullPath);t?vt(e):yt(e)}},e.prototype.getCurrentLocation=function(){return Lt(this.base)},e}(Ct);function Lt(t){var e=decodeURI(window.location.pathname);return t&&0===e.toLowerCase().indexOf(t.toLowerCase())&&(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}var $t=function(t){function e(e,r,n){t.call(this,e,r),n&&function(t){var e=Lt(t);if(!/^\/#/.test(e))return window.location.replace(x(t+"/#"+e)),!0}(this.base)||Pt()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router.options.scrollBehavior,r=dt&&e;r&&this.listeners.push(ot());var n=function(){var e=t.current;Pt()&&t.transitionTo(Tt(),function(n){r&&it(t.router,n,e,!0),dt||It(n.fullPath)})},o=dt?"popstate":"hashchange";window.addEventListener(o,n),this.listeners.push(function(){window.removeEventListener(o,n)})}},e.prototype.push=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){Ut(t.fullPath),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.replace=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){It(t.fullPath),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;Tt()!==e&&(t?Ut(e):It(e))},e.prototype.getCurrentLocation=function(){return Tt()},e}(Ct);function Pt(){var t=Tt();return"/"===t.charAt(0)||(It("/"+t),!1)}function Tt(){var t=window.location.href,e=t.indexOf("#");if(e<0)return"";var r=(t=t.slice(e+1)).indexOf("?");if(r<0){var n=t.indexOf("#");t=n>-1?decodeURI(t.slice(0,n))+t.slice(n):decodeURI(t)}else t=decodeURI(t.slice(0,r))+t.slice(r);return t}function qt(t){var e=window.location.href,r=e.indexOf("#");return(r>=0?e.slice(0,r):e)+"#"+t}function Ut(t){dt?vt(qt(t)):window.location.hash=t}function It(t){dt?yt(qt(t)):window.location.replace(qt(t))}var Mt=function(t){function r(e,r){t.call(this,e,r),this.stack=[],this.index=-1}return t&&(r.__proto__=t),r.prototype=Object.create(t&&t.prototype),r.prototype.constructor=r,r.prototype.push=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index+1).concat(t),n.index++,e&&e(t)},r)},r.prototype.replace=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index).concat(t),e&&e(t)},r)},r.prototype.go=function(t){var r=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var o=this.stack[n];this.confirmTransition(o,function(){r.index=n,r.updateRoute(o)},function(t){e(t,kt.duplicated)&&(r.index=n)})}},r.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},r.prototype.ensureURL=function(){},r}(Ct),Vt=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Y(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!dt&&!1!==t.fallback,this.fallback&&(e="hash"),J||(e="abstract"),this.mode=e,e){case"history":this.history=new St(this,t.base);break;case"hash":this.history=new $t(this,t.base,this.fallback);break;case"abstract":this.history=new Mt(this,t.base)}},Bt={currentRoute:{configurable:!0}};function Ht(t,e){return t.push(e),function(){var r=t.indexOf(e);r>-1&&t.splice(r,1)}}return Vt.prototype.match=function(t,e,r){return this.matcher.match(t,e,r)},Bt.currentRoute.get=function(){return this.history&&this.history.current},Vt.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",function(){var r=e.apps.indexOf(t);r>-1&&e.apps.splice(r,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardownListeners()}),!this.app){this.app=t;var r=this.history;if(r instanceof St||r instanceof $t){var n=function(){r.setupListeners()};r.transitionTo(r.getCurrentLocation(),n,n)}r.listen(function(t){e.apps.forEach(function(e){e._route=t})})}},Vt.prototype.beforeEach=function(t){return Ht(this.beforeHooks,t)},Vt.prototype.beforeResolve=function(t){return Ht(this.resolveHooks,t)},Vt.prototype.afterEach=function(t){return Ht(this.afterHooks,t)},Vt.prototype.onReady=function(t,e){this.history.onReady(t,e)},Vt.prototype.onError=function(t){this.history.onError(t)},Vt.prototype.push=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.push(t,e,r)});this.history.push(t,e,r)},Vt.prototype.replace=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.replace(t,e,r)});this.history.replace(t,e,r)},Vt.prototype.go=function(t){this.history.go(t)},Vt.prototype.back=function(){this.go(-1)},Vt.prototype.forward=function(){this.go(1)},Vt.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map(function(t){return Object.keys(t.components).map(function(e){return t.components[e]})})):[]},Vt.prototype.resolve=function(t,e,r){var n=B(t,e=e||this.history.current,r,this),o=this.match(n,e),i=o.redirectedFrom||o.fullPath;return{location:n,route:o,href:function(t,e,r){var n="hash"===r?"#"+e:e;return t?x(t+"/"+n):n}(this.history.base,i,this.mode),normalizedTo:n,resolved:o}},Vt.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==v&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Vt.prototype,Bt),Vt.install=function t(e){if(!t.installed||H!==e){t.installed=!0,H=e;var r=function(t){return void 0!==t},o=function(t,e){var n=t.$options._parentVnode;r(n)&&r(n=n.data)&&r(n=n.registerRouteInstance)&&n(t,e)};e.mixin({beforeCreate:function(){r(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,o(this,this)},destroyed:function(){o(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",n),e.component("RouterLink",D);var i=e.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created}},Vt.version="3.3.0",J&&window.Vue&&window.Vue.use(Vt),Vt},"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).VueRouter=e(); \ No newline at end of file From d1f05068a56220dcee8d1277801e4fedc4ba7029 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 27 May 2020 12:19:29 +0200 Subject: [PATCH 15/15] chore(release): 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ca993a2a1..788018e93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-router", - "version": "3.2.0", + "version": "3.3.0", "description": "Official router for Vue.js 2", "author": "Evan You", "license": "MIT",