From e24c57f5422eb8ad14b79707ccd60c805d409621 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 5 Jun 2020 20:44:32 +0200 Subject: [PATCH 01/17] docs: add navigation failures --- docs/.vuepress/config.js | 131 ++++++++++++--------- docs/guide/advanced/navigation-failures.md | 53 +++++++++ 2 files changed, 127 insertions(+), 57 deletions(-) create mode 100644 docs/guide/advanced/navigation-failures.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 636f2a240..365261eb6 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -26,23 +26,39 @@ module.exports = { description: 'Vue.js 공식 라우터' }, '/fr/': { - lang: 'fr', - title: 'Vue Router', - description: 'Routeur officiel pour Vue.Js' + lang: 'fr', + title: 'Vue Router', + description: 'Routeur officiel pour Vue.Js' } }, head: [ ['link', { rel: 'icon', href: `/logo.png` }], - ['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }], - ['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }], - ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }], + [ + 'link', + { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` } + ], + [ + 'link', + { + rel: 'mask-icon', + href: '/icons/safari-pinned-tab.svg', + color: '#3eaf7c' + } + ], + [ + 'meta', + { + name: 'msapplication-TileImage', + content: '/icons/msapplication-icon-144x144.png' + } + ] ], serviceWorker: true, theme: 'vue', themeConfig: { algolia: { apiKey: 'f854bb46d3de7eeb921a3b9173bd0d4c', - indexName: 'vue-router', + indexName: 'vue-router' }, repo: 'vuejs/vue-router', docsDir: 'docs', @@ -92,7 +108,8 @@ module.exports = { '/guide/advanced/transitions.md', '/guide/advanced/data-fetching.md', '/guide/advanced/scroll-behavior.md', - '/guide/advanced/lazy-loading.md' + '/guide/advanced/lazy-loading.md', + '/guide/advanced/navigation-failures.md' ] } ] @@ -298,55 +315,55 @@ module.exports = { ] }, '/fr/': { - label: 'Français', - selectText: 'Langues', - editLinkText: 'Editer cette page sur Github', - nav: [ - { - text: 'Guide', - link: '/fr/guide/' - }, - { - text: 'API', - link: '/fr/api/' - }, - { - text: 'Notes de version', - link: 'https://github.com/vuejs/vue-router/releases' - } - ], - sidebar: [ - '/fr/installation.md', - '/fr/', - { - title: 'Essentiels', - collapsable: false, - children: [ - '/fr/guide/', - '/fr/guide/essentials/dynamic-matching.md', - '/fr/guide/essentials/nested-routes.md', - '/fr/guide/essentials/navigation.md', - '/fr/guide/essentials/named-routes.md', - '/fr/guide/essentials/named-views.md', - '/fr/guide/essentials/redirect-and-alias.md', - '/fr/guide/essentials/passing-props.md', - '/fr/guide/essentials/history-mode.md' - ] - }, - { - title: 'Avancés', - collapsable: false, - children: [ - '/fr/guide/advanced/navigation-guards.md', - '/fr/guide/advanced/meta.md', - '/fr/guide/advanced/transitions.md', - '/fr/guide/advanced/data-fetching.md', - '/fr/guide/advanced/scroll-behavior.md', - '/fr/guide/advanced/lazy-loading.md' - ] - } - ] - }, + label: 'Français', + selectText: 'Langues', + editLinkText: 'Editer cette page sur Github', + nav: [ + { + text: 'Guide', + link: '/fr/guide/' + }, + { + text: 'API', + link: '/fr/api/' + }, + { + text: 'Notes de version', + link: 'https://github.com/vuejs/vue-router/releases' + } + ], + sidebar: [ + '/fr/installation.md', + '/fr/', + { + title: 'Essentiels', + collapsable: false, + children: [ + '/fr/guide/', + '/fr/guide/essentials/dynamic-matching.md', + '/fr/guide/essentials/nested-routes.md', + '/fr/guide/essentials/navigation.md', + '/fr/guide/essentials/named-routes.md', + '/fr/guide/essentials/named-views.md', + '/fr/guide/essentials/redirect-and-alias.md', + '/fr/guide/essentials/passing-props.md', + '/fr/guide/essentials/history-mode.md' + ] + }, + { + title: 'Avancés', + collapsable: false, + children: [ + '/fr/guide/advanced/navigation-guards.md', + '/fr/guide/advanced/meta.md', + '/fr/guide/advanced/transitions.md', + '/fr/guide/advanced/data-fetching.md', + '/fr/guide/advanced/scroll-behavior.md', + '/fr/guide/advanced/lazy-loading.md' + ] + } + ] + } } } } diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md new file mode 100644 index 000000000..ef6d9bee8 --- /dev/null +++ b/docs/guide/advanced/navigation-failures.md @@ -0,0 +1,53 @@ +# Navigation Failures + +When using `router-link`, Vue Router internally calls `router.push` to trigger a navigation. Depending on the current location and existing [Navigation Guards](./navigation-guards.md), this navigation might end up in a new page being shown, but there are a couple of situations where we will stay on the same page: + +- We are already on the page we are trying to go to +- A navigation guard aborts the navigation by calling `next(false)` +- An error is thrown in a navigation by calling `next(new Error())` + +When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. This is part of the _Navigation Failures_ system in Vue Router and it is only available from version 3.2.0 onwards but existed for a long time before: through the two optional callbacks, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `osComplete` and rejects instead of invoking `onAbort`. + +## Detecting Navigation Failures + +_Navigation Failures_ are `Error` instances with a few extra properties. Among them, you can find a `type` property. This will allow you to check the type of the navigation failure: + +```js +import { NavigationFailureTypes } from 'vue-router' + +// trying to access an admin-only route +router.push('/admin').catch(failure => { + if (failure) { + if (failure.type === NavigationFailureTypes.redirected) { + // show a small notification to the user + showToast('Login in order to access the admin panel') + } + } +}) +``` + +## `NavigationFailureTypes` + +`NavigationFailureTypes` exposes the following properties to differentiate _Navigation Failures_: + +- `redirected`: `next(newLocation)` was called inside of a navigation guard to redirect somewhere else. +- `aborted`: `next(false)` was called inside of a navigation guard to the navigation. +- `cancelled`: A new navigation completely took place before the current navigation could finish. e.g. `router.push` was called while waiting inside of a navigation guard. +- `duplicated`: The navigation was prevented because we are already at the target location. + +## _Navigation Failures_'s properties + +Apart from exposing a `type` property, all navigation failures expose `to` and `from` properties to reflect the current location as well as the target location for the navigation that failed: + +```js +// given we are at `/` +router.push('/admin').catch(failure => { + if (failure) { + if (failure.type === NavigationFailureTypes.redirected) { + failure.to.path // '/admin' + failure.from.path // '/' + } + } +``` + +In all cases, `from` and `to` are normalized route locations. From e69d78bf0c28d999d649c8be89fea88faf83cb3d Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 10 Jun 2020 13:20:50 +0200 Subject: [PATCH 02/17] Update docs/guide/advanced/navigation-failures.md Co-authored-by: Natalia Tepluhina --- docs/guide/advanced/navigation-failures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index ef6d9bee8..d746bdff7 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -6,7 +6,7 @@ When using `router-link`, Vue Router internally calls `router.push` to trigger a - A navigation guard aborts the navigation by calling `next(false)` - An error is thrown in a navigation by calling `next(new Error())` -When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. This is part of the _Navigation Failures_ system in Vue Router and it is only available from version 3.2.0 onwards but existed for a long time before: through the two optional callbacks, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `osComplete` and rejects instead of invoking `onAbort`. +When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. This is part of the _Navigation Failures_ system in Vue Router and it is only available from version 3.2.0 onwards but existed for a long time before: through the two optional callbacks, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. ## Detecting Navigation Failures From a554157c3fd36a053031d70a1492b3e6fc40b675 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:09:33 +0200 Subject: [PATCH 03/17] Update docs/guide/advanced/navigation-failures.md --- docs/guide/advanced/navigation-failures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index d746bdff7..f9dd432e7 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -4,7 +4,7 @@ When using `router-link`, Vue Router internally calls `router.push` to trigger a - We are already on the page we are trying to go to - A navigation guard aborts the navigation by calling `next(false)` -- An error is thrown in a navigation by calling `next(new Error())` +- A navigation guard throws an error or calls `next(new Error())` When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. This is part of the _Navigation Failures_ system in Vue Router and it is only available from version 3.2.0 onwards but existed for a long time before: through the two optional callbacks, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. From 02f7eaac8c8f6d2de7589e202b3699f4a5e9e724 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:24:24 +0200 Subject: [PATCH 04/17] feat(errors): NavigationDuplicated name for backwards compatibility --- src/history/abstract.js | 2 +- src/history/base.js | 6 ++++-- src/{history => util}/errors.js | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) rename src/{history => util}/errors.js (91%) diff --git a/src/history/abstract.js b/src/history/abstract.js index 1add43d01..6de8ce269 100644 --- a/src/history/abstract.js +++ b/src/history/abstract.js @@ -3,7 +3,7 @@ import type Router from '../index' import { History } from './base' import { isRouterError } from '../util/warn' -import { NavigationFailureType } from './errors' +import { NavigationFailureType } from '../util/errors' export class AbstractHistory extends History { index: number diff --git a/src/history/base.js b/src/history/base.js index 7425df13a..d1c299561 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -4,7 +4,7 @@ import { _Vue } from '../install' import type Router from '../index' import { inBrowser } from '../util/dom' import { runQueue } from '../util/async' -import { warn, isError, isRouterError } from '../util/warn' +import { warn } from '../util/warn' import { START, isSameRoute } from '../util/route' import { flatten, @@ -16,8 +16,10 @@ import { createNavigationCancelledError, createNavigationRedirectedError, createNavigationAbortedError, + isError, + isRouterError, NavigationFailureType -} from './errors' +} from '../util/errors' export class History { router: Router diff --git a/src/history/errors.js b/src/util/errors.js similarity index 91% rename from src/history/errors.js rename to src/util/errors.js index 0eaa5a8df..140f181fa 100644 --- a/src/history/errors.js +++ b/src/util/errors.js @@ -17,12 +17,15 @@ export function createNavigationRedirectedError (from, to) { } export function createNavigationDuplicatedError (from, to) { - return createRouterError( + const error = createRouterError( from, to, NavigationFailureType.duplicated, `Avoided redundant navigation to current location: "${from.fullPath}".` ) + // backwards compatible with the first introduction of Errors + error.name = 'NavigationDuplicated' + return error } export function createNavigationCancelledError (from, to) { From a93434f48f8dc79823a01ea90a450ad72dc43522 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:31:37 +0200 Subject: [PATCH 05/17] docs: use singular form --- docs/guide/advanced/navigation-failures.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index f9dd432e7..ef759a89e 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -13,12 +13,12 @@ When using a regular `router-link`, **none of these failures will log an error** _Navigation Failures_ are `Error` instances with a few extra properties. Among them, you can find a `type` property. This will allow you to check the type of the navigation failure: ```js -import { NavigationFailureTypes } from 'vue-router' +import { NavigationFailureType } from 'vue-router' // trying to access an admin-only route router.push('/admin').catch(failure => { if (failure) { - if (failure.type === NavigationFailureTypes.redirected) { + if (failure.type === NavigationFailureType.redirected) { // show a small notification to the user showToast('Login in order to access the admin panel') } @@ -26,9 +26,9 @@ router.push('/admin').catch(failure => { }) ``` -## `NavigationFailureTypes` +## `NavigationFailureType` -`NavigationFailureTypes` exposes the following properties to differentiate _Navigation Failures_: +`NavigationFailureType` exposes the following properties to differentiate _Navigation Failures_: - `redirected`: `next(newLocation)` was called inside of a navigation guard to redirect somewhere else. - `aborted`: `next(false)` was called inside of a navigation guard to the navigation. @@ -43,7 +43,7 @@ Apart from exposing a `type` property, all navigation failures expose `to` and ` // given we are at `/` router.push('/admin').catch(failure => { if (failure) { - if (failure.type === NavigationFailureTypes.redirected) { + if (failure.type === NavigationFailureType.redirected) { failure.to.path // '/admin' failure.from.path // '/' } From cea40b27cb9fcac34114c93548da038c28d94926 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:40:17 +0200 Subject: [PATCH 06/17] feat(errors): expose isRouterError --- docs/guide/advanced/navigation-failures.md | 16 +++-- src/history/abstract.js | 3 +- src/index.js | 80 ++++++++++++---------- src/util/errors.js | 8 +++ src/util/resolve-components.js | 3 +- src/util/warn.js | 7 -- 6 files changed, 63 insertions(+), 54 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index ef759a89e..28274408e 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -10,15 +10,15 @@ When using a regular `router-link`, **none of these failures will log an error** ## Detecting Navigation Failures -_Navigation Failures_ are `Error` instances with a few extra properties. Among them, you can find a `type` property. This will allow you to check the type of the navigation failure: +_Navigation Failures_ are `Error` instances with a few extra properties. To check if an error comes from the Router, use the `isRouterError` function: ```js -import { NavigationFailureType } from 'vue-router' +import { NavigationFailureType, isRouterError } from 'vue-router' -// trying to access an admin-only route +// trying to access the admin page router.push('/admin').catch(failure => { if (failure) { - if (failure.type === NavigationFailureType.redirected) { + if (isRouterError(failure, NavigationFailureType.redirected)) { // show a small notification to the user showToast('Login in order to access the admin panel') } @@ -26,6 +26,10 @@ router.push('/admin').catch(failure => { }) ``` +::: tip +If you omit the second parameter: `isRouterError(failure)`, it will only check if the error comes from the Router. +::: + ## `NavigationFailureType` `NavigationFailureType` exposes the following properties to differentiate _Navigation Failures_: @@ -40,10 +44,10 @@ router.push('/admin').catch(failure => { Apart from exposing a `type` property, all navigation failures expose `to` and `from` properties to reflect the current location as well as the target location for the navigation that failed: ```js -// given we are at `/` +// trying to access the admin page router.push('/admin').catch(failure => { if (failure) { - if (failure.type === NavigationFailureType.redirected) { + if (isRouterError(failure, NavigationFailureType.redirected)) { failure.to.path // '/admin' failure.from.path // '/' } diff --git a/src/history/abstract.js b/src/history/abstract.js index 6de8ce269..6e8410a30 100644 --- a/src/history/abstract.js +++ b/src/history/abstract.js @@ -2,8 +2,7 @@ import type Router from '../index' import { History } from './base' -import { isRouterError } from '../util/warn' -import { NavigationFailureType } from '../util/errors' +import { NavigationFailureType, isRouterError } from '../util/errors' export class AbstractHistory extends History { index: number diff --git a/src/index.js b/src/index.js index ae3b69e15..1debc3016 100644 --- a/src/index.js +++ b/src/index.js @@ -16,22 +16,24 @@ import { AbstractHistory } from './history/abstract' import type { Matcher } from './create-matcher' +import { isRouterError, NavigationFailureType } from './util/errors' + export default class VueRouter { - static install: () => void; - static version: string; - - app: any; - apps: Array; - ready: boolean; - readyCbs: Array; - options: RouterOptions; - mode: string; - history: HashHistory | HTML5History | AbstractHistory; - matcher: Matcher; - fallback: boolean; - beforeHooks: Array; - resolveHooks: Array; - afterHooks: Array; + static install: () => void + static version: string + + app: any + apps: Array + ready: boolean + readyCbs: Array + options: RouterOptions + mode: string + history: HashHistory | HTML5History | AbstractHistory + matcher: Matcher + fallback: boolean + beforeHooks: Array + resolveHooks: Array + afterHooks: Array constructor (options: RouterOptions = {}) { this.app = null @@ -43,7 +45,8 @@ export default class VueRouter { this.matcher = createMatcher(options.routes || [], this) let mode = options.mode || 'hash' - this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false + this.fallback = + mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } @@ -69,11 +72,7 @@ export default class VueRouter { } } - match ( - raw: RawLocation, - current?: Route, - redirectedFrom?: Location - ): Route { + match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route { return this.matcher.match(raw, current, redirectedFrom) } @@ -82,11 +81,12 @@ export default class VueRouter { } init (app: any /* Vue component instance */) { - process.env.NODE_ENV !== 'production' && assert( - install.installed, - `not installed. Make sure to call \`Vue.use(VueRouter)\` ` + - `before creating root instance.` - ) + process.env.NODE_ENV !== 'production' && + assert( + install.installed, + `not installed. Make sure to call \`Vue.use(VueRouter)\` ` + + `before creating root instance.` + ) this.apps.push(app) @@ -131,11 +131,15 @@ export default class VueRouter { history.setupListeners() handleInitialScroll(routeOrError) } - history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners) + history.transitionTo( + history.getCurrentLocation(), + setupListeners, + setupListeners + ) } history.listen(route => { - this.apps.forEach((app) => { + this.apps.forEach(app => { app._route = route }) }) @@ -204,11 +208,14 @@ export default class VueRouter { if (!route) { return [] } - return [].concat.apply([], route.matched.map(m => { - return Object.keys(m.components).map(key => { - return m.components[key] + return [].concat.apply( + [], + route.matched.map(m => { + return Object.keys(m.components).map(key => { + return m.components[key] + }) }) - })) + ) } resolve ( @@ -224,12 +231,7 @@ export default class VueRouter { resolved: Route } { current = current || this.history.current - const location = normalizeLocation( - to, - current, - append, - this - ) + const location = normalizeLocation(to, current, append, this) const route = this.match(location, current) const fullPath = route.redirectedFrom || route.fullPath const base = this.history.base @@ -267,6 +269,8 @@ function createHref (base: string, fullPath: string, mode) { VueRouter.install = install VueRouter.version = '__VERSION__' +VueRouter.isRouterError = isRouterError +VueRouter.NavigationFailureType = NavigationFailureType if (inBrowser && window.Vue) { window.Vue.use(VueRouter) diff --git a/src/util/errors.js b/src/util/errors.js index 140f181fa..9bc8b7a14 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -71,3 +71,11 @@ function stringifyRoute (to) { }) return JSON.stringify(location, null, 2) } + +export function isError (err) { + return Object.prototype.toString.call(err).indexOf('Error') > -1 +} + +export function isRouterError (err, errorType) { + return isError(err) && err._isRouter && (errorType == null || err.type === errorType) +} diff --git a/src/util/resolve-components.js b/src/util/resolve-components.js index 3f7608cd5..204e3ca8f 100644 --- a/src/util/resolve-components.js +++ b/src/util/resolve-components.js @@ -1,7 +1,8 @@ /* @flow */ import { _Vue } from '../install' -import { warn, isError } from './warn' +import { warn } from './warn' +import { isError } from '../util/errors' export function resolveAsyncComponents (matched: Array): Function { return (to, from, next) => { diff --git a/src/util/warn.js b/src/util/warn.js index 73e70caf8..025d3b20f 100644 --- a/src/util/warn.js +++ b/src/util/warn.js @@ -12,10 +12,3 @@ export function warn (condition: any, message: string) { } } -export function isError (err: any): boolean { - return Object.prototype.toString.call(err).indexOf('Error') > -1 -} - -export function isRouterError (err: any, errorType: ?string): boolean { - return isError(err) && err._isRouter && (errorType == null || err.type === errorType) -} From c567ef59a1767a9ea61c2a474586882fca8f31e6 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:46:56 +0200 Subject: [PATCH 07/17] refactor: fix flow types --- src/index.js | 2 ++ test/unit/specs/error-handling.spec.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 1debc3016..75dcfeff6 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,8 @@ import { isRouterError, NavigationFailureType } from './util/errors' export default class VueRouter { static install: () => void static version: string + static isRouterError: Function + static NavigationFailureType: any app: any apps: Array diff --git a/test/unit/specs/error-handling.spec.js b/test/unit/specs/error-handling.spec.js index 003dad6f9..2f0589f4d 100644 --- a/test/unit/specs/error-handling.spec.js +++ b/test/unit/specs/error-handling.spec.js @@ -1,6 +1,6 @@ import Vue from 'vue' import VueRouter from '../../../src/index' -import { NavigationFailureType } from '../../../src/history/errors' +import { NavigationFailureType } from '../../../src/util/errors' Vue.use(VueRouter) From c47f9c43dfe323c9373bc84d220736677655910a Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 13:47:16 +0200 Subject: [PATCH 08/17] types(errors): add TS types --- types/router.d.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/types/router.d.ts b/types/router.d.ts index de0e327fe..50780140e 100644 --- a/types/router.d.ts +++ b/types/router.d.ts @@ -61,6 +61,17 @@ export declare class VueRouter { } static install: PluginFunction + static version: string + + static isRouterError: (error: any, type?: NavigationFailureTypeE) => error is Error + static NavigationFailureType: NavigationFailureTypeE +} + +export enum NavigationFailureTypeE { + redirected = 1, + aborted = 2, + cancelled = 3, + duplicated = 4 } type Position = { x: number; y: number } From bb90585a97b3c41679c55841ecbba4ee087c0d11 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 14:38:41 +0200 Subject: [PATCH 09/17] docs: add new in comment [skip ci] --- docs/guide/advanced/navigation-failures.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index 28274408e..eb646c3c7 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -1,5 +1,7 @@ # Navigation Failures +> New in 3.4.0 + When using `router-link`, Vue Router internally calls `router.push` to trigger a navigation. Depending on the current location and existing [Navigation Guards](./navigation-guards.md), this navigation might end up in a new page being shown, but there are a couple of situations where we will stay on the same page: - We are already on the page we are trying to go to From 902c82d4c0043847d446e1141caae6c185844276 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 14:46:07 +0200 Subject: [PATCH 10/17] docs: rename isRouterError [skip ci] --- docs/guide/advanced/navigation-failures.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index eb646c3c7..52ed273e6 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -12,15 +12,15 @@ When using a regular `router-link`, **none of these failures will log an error** ## Detecting Navigation Failures -_Navigation Failures_ are `Error` instances with a few extra properties. To check if an error comes from the Router, use the `isRouterError` function: +_Navigation Failures_ are `Error` instances with a few extra properties. To check if an error comes from the Router, use the `isNavigationFailure` function: ```js -import { NavigationFailureType, isRouterError } from 'vue-router' +import { NavigationFailureType, isNavigationFailure } from 'vue-router' // trying to access the admin page router.push('/admin').catch(failure => { if (failure) { - if (isRouterError(failure, NavigationFailureType.redirected)) { + if (isNavigationFailure(failure, NavigationFailureType.redirected)) { // show a small notification to the user showToast('Login in order to access the admin panel') } @@ -29,7 +29,7 @@ router.push('/admin').catch(failure => { ``` ::: tip -If you omit the second parameter: `isRouterError(failure)`, it will only check if the error comes from the Router. +If you omit the second parameter: `isNavigationFailure(failure)`, it will only check if the error comes from the Router. ::: ## `NavigationFailureType` @@ -48,12 +48,11 @@ Apart from exposing a `type` property, all navigation failures expose `to` and ` ```js // trying to access the admin page router.push('/admin').catch(failure => { - if (failure) { - if (isRouterError(failure, NavigationFailureType.redirected)) { - failure.to.path // '/admin' - failure.from.path // '/' - } + if (isNavigationFailure(failure, NavigationFailureType.redirected)) { + failure.to.path // '/admin' + failure.from.path // '/' } +}) ``` In all cases, `from` and `to` are normalized route locations. From e532401a7c1c946e835c4c5db837400c831a1693 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 14:49:01 +0200 Subject: [PATCH 11/17] docs: remove extra if [skip ci] --- docs/guide/advanced/navigation-failures.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index 52ed273e6..7fc48aae7 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -19,11 +19,9 @@ import { NavigationFailureType, isNavigationFailure } from 'vue-router' // trying to access the admin page router.push('/admin').catch(failure => { - if (failure) { - if (isNavigationFailure(failure, NavigationFailureType.redirected)) { - // show a small notification to the user - showToast('Login in order to access the admin panel') - } + if (isNavigationFailure(failure, NavigationFailureType.redirected)) { + // show a small notification to the user + showToast('Login in order to access the admin panel') } }) ``` @@ -43,7 +41,7 @@ If you omit the second parameter: `isNavigationFailure(failure)`, it will only c ## _Navigation Failures_'s properties -Apart from exposing a `type` property, all navigation failures expose `to` and `from` properties to reflect the current location as well as the target location for the navigation that failed: +All navigation failures expose `to` and `from` properties to reflect the current location as well as the target location for the navigation that failed: ```js // trying to access the admin page From 5e03c901388ebc8a6b670445611f1e92baab35b5 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 24 Jun 2020 15:02:20 +0200 Subject: [PATCH 12/17] docs: split background story and explanation [skip ci] --- docs/guide/advanced/navigation-failures.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index 7fc48aae7..427cc607c 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -8,7 +8,11 @@ When using `router-link`, Vue Router internally calls `router.push` to trigger a - A navigation guard aborts the navigation by calling `next(false)` - A navigation guard throws an error or calls `next(new Error())` -When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. This is part of the _Navigation Failures_ system in Vue Router and it is only available from version 3.2.0 onwards but existed for a long time before: through the two optional callbacks, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. +When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. Let's understand how to differentiate _Navigation Failures_. + +::: tip Background story + _Navigation Failures_ were exposed on version 3.2.0 but existed for a long time before: through the two optional callbacks of `router.push`, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. + ::: ## Detecting Navigation Failures @@ -27,7 +31,7 @@ router.push('/admin').catch(failure => { ``` ::: tip -If you omit the second parameter: `isNavigationFailure(failure)`, it will only check if the error comes from the Router. +If you omit the second parameter: `isNavigationFailure(failure)`, it will only check if the error is a _Navigation Failure_. ::: ## `NavigationFailureType` From 4492bbb745e34a5f4d8355d5f57389a3a90ce4a7 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 30 Jun 2020 16:30:08 +0200 Subject: [PATCH 13/17] Apply suggestions from code review Co-authored-by: Ben Hong --- docs/guide/advanced/navigation-failures.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index 427cc607c..135196c14 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -2,16 +2,16 @@ > New in 3.4.0 -When using `router-link`, Vue Router internally calls `router.push` to trigger a navigation. Depending on the current location and existing [Navigation Guards](./navigation-guards.md), this navigation might end up in a new page being shown, but there are a couple of situations where we will stay on the same page: +When using `router-link`, Vue Router calls `router.push` to trigger a navigation. While the expected behavior for most links is to navigate a user to a new page, there are a few situations where users will remain on the same page: - We are already on the page we are trying to go to -- A navigation guard aborts the navigation by calling `next(false)` -- A navigation guard throws an error or calls `next(new Error())` +- A [navigation guard](./navigation-guards.md) aborts the navigation by calling `next(false)` +- A [navigation guard](./navigation-guards.md) throws an error or calls `next(new Error())` When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. Let's understand how to differentiate _Navigation Failures_. ::: tip Background story - _Navigation Failures_ were exposed on version 3.2.0 but existed for a long time before: through the two optional callbacks of `router.push`, `onComplete` and `onAbort` that can be passed to `router.push`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. + In v3.2.0, _Navigation Failures_ were exposed through the two optional callbacks of `router.push`: `onComplete` and `onAbort`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. ::: ## Detecting Navigation Failures @@ -36,7 +36,7 @@ If you omit the second parameter: `isNavigationFailure(failure)`, it will only c ## `NavigationFailureType` -`NavigationFailureType` exposes the following properties to differentiate _Navigation Failures_: +`NavigationFailureType` help developers to differentiate between the various types of _Navigation Failures_. There are four different types: - `redirected`: `next(newLocation)` was called inside of a navigation guard to redirect somewhere else. - `aborted`: `next(false)` was called inside of a navigation guard to the navigation. @@ -57,4 +57,4 @@ router.push('/admin').catch(failure => { }) ``` -In all cases, `from` and `to` are normalized route locations. +In all cases, `to` and `from` are normalized route locations. From 73c1d58af598bbcaed005d5534592f2c89cea29f Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 3 Jul 2020 23:10:20 +0200 Subject: [PATCH 14/17] refactor: adapt values for errors --- src/util/errors.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/errors.js b/src/util/errors.js index 9bc8b7a14..355f2cb19 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -1,8 +1,8 @@ export const NavigationFailureType = { - redirected: 1, - aborted: 2, - cancelled: 3, - duplicated: 4 + redirected: 2, + aborted: 4, + cancelled: 8, + duplicated: 16 } export function createNavigationRedirectedError (from, to) { From 7ef00d16046b8369f6232d70c24a8d5881b057b5 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 31 Jul 2020 21:01:08 +0200 Subject: [PATCH 15/17] Apply suggestions from code review Co-authored-by: Ben Hong --- docs/guide/advanced/navigation-failures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/advanced/navigation-failures.md b/docs/guide/advanced/navigation-failures.md index 135196c14..d784a4aec 100644 --- a/docs/guide/advanced/navigation-failures.md +++ b/docs/guide/advanced/navigation-failures.md @@ -4,11 +4,11 @@ When using `router-link`, Vue Router calls `router.push` to trigger a navigation. While the expected behavior for most links is to navigate a user to a new page, there are a few situations where users will remain on the same page: -- We are already on the page we are trying to go to +- Users are already on the page that they are trying to navigate to - A [navigation guard](./navigation-guards.md) aborts the navigation by calling `next(false)` - A [navigation guard](./navigation-guards.md) throws an error or calls `next(new Error())` -When using a regular `router-link`, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. Let's understand how to differentiate _Navigation Failures_. +When using a `router-link` component, **none of these failures will log an error**. However, if you are using `router.push` or `router.replace`, you might come across an _"Uncaught (in promise) Error"_ message followed by a more specific message in your console. Let's understand how to differentiate _Navigation Failures_. ::: tip Background story In v3.2.0, _Navigation Failures_ were exposed through the two optional callbacks of `router.push`: `onComplete` and `onAbort`. Since version 3.1.0, `router.push` and `router.replace` return a _Promise_ if no `onComplete`/`onAbort` callback is provided. This _Promise_ resolves instead of invoking `onComplete` and rejects instead of invoking `onAbort`. From 7fd4b93d9fc2316ba6e3e9c31bd3a060a6ebf6dc Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 31 Jul 2020 21:04:16 +0200 Subject: [PATCH 16/17] refactor: isRouterError -> isNavigationFailure --- src/history/abstract.js | 4 ++-- src/history/base.js | 12 ++++++++---- src/index.js | 10 +++++----- src/util/errors.js | 8 ++++++-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/history/abstract.js b/src/history/abstract.js index 6e8410a30..325e4b954 100644 --- a/src/history/abstract.js +++ b/src/history/abstract.js @@ -2,7 +2,7 @@ import type Router from '../index' import { History } from './base' -import { NavigationFailureType, isRouterError } from '../util/errors' +import { NavigationFailureType, isNavigationFailure } from '../util/errors' export class AbstractHistory extends History { index: number @@ -50,7 +50,7 @@ export class AbstractHistory extends History { this.updateRoute(route) }, err => { - if (isRouterError(err, NavigationFailureType.duplicated)) { + if (isNavigationFailure(err, NavigationFailureType.duplicated)) { this.index = targetIndex } } diff --git a/src/history/base.js b/src/history/base.js index d1c299561..072cfb8e8 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -17,7 +17,7 @@ import { createNavigationRedirectedError, createNavigationAbortedError, isError, - isRouterError, + isNavigationFailure, NavigationFailureType } from '../util/errors' @@ -37,7 +37,11 @@ export class History { // implemented by sub-classes +go: (n: number) => void +push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void - +replace: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void + +replace: ( + loc: RawLocation, + onComplete?: Function, + onAbort?: Function + ) => void +ensureURL: (push?: boolean) => void +getCurrentLocation: () => string +setupListeners: Function @@ -117,7 +121,7 @@ export class History { this.ready = true // Initial redirection should still trigger the onReady onSuccess // https://github.com/vuejs/vue-router/issues/3225 - if (!isRouterError(err, NavigationFailureType.redirected)) { + if (!isNavigationFailure(err, NavigationFailureType.redirected)) { this.readyErrorCbs.forEach(cb => { cb(err) }) @@ -137,7 +141,7 @@ export class History { // changed after adding errors with // https://github.com/vuejs/vue-router/pull/3047 before that change, // redirect and aborted navigation would produce an err == null - if (!isRouterError(err) && isError(err)) { + if (!isNavigationFailure(err) && isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err) diff --git a/src/index.js b/src/index.js index 75dcfeff6..22ecff96d 100644 --- a/src/index.js +++ b/src/index.js @@ -16,12 +16,12 @@ import { AbstractHistory } from './history/abstract' import type { Matcher } from './create-matcher' -import { isRouterError, NavigationFailureType } from './util/errors' +import { isNavigationFailure, NavigationFailureType } from './util/errors' export default class VueRouter { static install: () => void static version: string - static isRouterError: Function + static isNavigationFailure: Function static NavigationFailureType: any app: any @@ -120,7 +120,7 @@ export default class VueRouter { const history = this.history if (history instanceof HTML5History || history instanceof HashHistory) { - const handleInitialScroll = (routeOrError) => { + const handleInitialScroll = routeOrError => { const from = history.current const expectScroll = this.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll @@ -129,7 +129,7 @@ export default class VueRouter { handleScroll(this, routeOrError, from, false) } } - const setupListeners = (routeOrError) => { + const setupListeners = routeOrError => { history.setupListeners() handleInitialScroll(routeOrError) } @@ -271,7 +271,7 @@ function createHref (base: string, fullPath: string, mode) { VueRouter.install = install VueRouter.version = '__VERSION__' -VueRouter.isRouterError = isRouterError +VueRouter.isNavigationFailure = isNavigationFailure VueRouter.NavigationFailureType = NavigationFailureType if (inBrowser && window.Vue) { diff --git a/src/util/errors.js b/src/util/errors.js index 355f2cb19..bacf6881e 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -76,6 +76,10 @@ export function isError (err) { return Object.prototype.toString.call(err).indexOf('Error') > -1 } -export function isRouterError (err, errorType) { - return isError(err) && err._isRouter && (errorType == null || err.type === errorType) +export function isNavigationFailure (err, errorType) { + return ( + isError(err) && + err._isRouter && + (errorType == null || err.type === errorType) + ) } From d8ce259b0ec3feabf356f99b22bfd919fb8a07dc Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 31 Jul 2020 21:09:47 +0200 Subject: [PATCH 17/17] refactor: add missing type rename [skip ci] --- types/router.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/router.d.ts b/types/router.d.ts index 50780140e..07f912eb6 100644 --- a/types/router.d.ts +++ b/types/router.d.ts @@ -63,7 +63,7 @@ export declare class VueRouter { static install: PluginFunction static version: string - static isRouterError: (error: any, type?: NavigationFailureTypeE) => error is Error + static isNavigationFailure: (error: any, type?: NavigationFailureTypeE) => error is Error static NavigationFailureType: NavigationFailureTypeE }