From: Eduardo San Martin Morote Date: Fri, 5 Jul 2019 18:21:16 +0000 (+0200) Subject: fix(errors): fix error inheritance in IE<10 X-Git-Tag: v4.0.0-alpha.0~310 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=08bd7760c2aa8148e1bda394a76c9afb70d403c2;p=thirdparty%2Fvuejs%2Frouter.git fix(errors): fix error inheritance in IE<10 --- diff --git a/__tests__/matcher.spec.js b/__tests__/matcher.spec.js index 3fb51359..3f5fd9b3 100644 --- a/__tests__/matcher.spec.js +++ b/__tests__/matcher.spec.js @@ -138,7 +138,7 @@ describe('Router Matcher', () => { expect( assertErrorMatch({ path: '/', component }, { path: '/foo' }) ).toMatchInlineSnapshot( - `[Error: No match for {"path":"/foo","params":{},"query":{},"hash":"","fullPath":"/"}]` + `[NoRouteMatchError: No match for {"path":"/foo","params":{},"query":{},"hash":"","fullPath":"/"}]` ) }) }) @@ -164,7 +164,7 @@ describe('Router Matcher', () => { expect( assertErrorMatch({ path: '/', component }, { name: 'Home' }) ).toMatchInlineSnapshot( - `[Error: No match for {"path":"/","name":"Home","params":{},"query":{},"hash":"","fullPath":"/"}]` + `[NoRouteMatchError: No match for {"path":"/","name":"Home","params":{},"query":{},"hash":"","fullPath":"/"}]` ) }) }) @@ -411,12 +411,12 @@ describe('Router Matcher', () => { { path: '/redirect', params: {}, matched: [], name: undefined } ) ).toMatchInlineSnapshot(` - [Error: Cannot redirect using a relative location: - { - "params": {} - } - Use the function redirect and explicitely provide a name] - `) + [InvalidRouteMatch: Cannot redirect using a relative location: + { + "params": {} + } + Use the function redirect and explicitely provide a name] + `) }) it('normalize a location when redirecting', () => { @@ -466,7 +466,7 @@ describe('Router Matcher', () => { { ...start, matched: start.matched.map(normalizeRouteRecord) } ) ).toMatchInlineSnapshot( - `[Error: No match for {"name":"home","params":{},"path":"/"}]` + `[NoRouteMatchError: No match for {"name":"home","params":{},"path":"/"}]` ) }) }) diff --git a/explorations/tsconfig.json b/explorations/tsconfig.json index 0a690fa2..85d58943 100644 --- a/explorations/tsconfig.json +++ b/explorations/tsconfig.json @@ -1,7 +1,7 @@ { "include": ["./**/*.ts"], "compilerOptions": { - "target": "es3" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, // "lib": ["es2017.object"] /* Specify library files to be included in the compilation. */, // "declaration": true /* Generates corresponding '.d.ts' file. */, diff --git a/package.json b/package.json index 86d78aa8..619e0530 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "jsdom": "^15.1.1", "mocha": "^6.1.3", "prettier": "^1.18.2", - "rollup": "^1.16.3", + "rollup": "^1.16.6", "rollup-plugin-alias": "^1.5.2", "rollup-plugin-commonjs": "^10.0.1", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-replace": "^2.2.0", - "rollup-plugin-terser": "^5.0.0", + "rollup-plugin-terser": "^5.1.0", "rollup-plugin-typescript2": "^0.21.2", "ts-jest": "^24.0.2", "ts-loader": "^6.0.4", diff --git a/rollup.config.js b/rollup.config.js index 39d33167..f771eca3 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -77,7 +77,7 @@ function createEntry( // same for d.ts files declaration: format === 'es' && isBrowser && !minify, module: 'esnext', // we need to override it because mocha requires this value to be commonjs - target: format === 'iife' || format === 'cjs' ? 'es3' : 'esnext', + target: format === 'iife' || format === 'cjs' ? 'es5' : 'esnext', }, }, }) diff --git a/src/consola.ts b/src/consola.ts index 0929e402..9c884a89 100644 --- a/src/consola.ts +++ b/src/consola.ts @@ -1,4 +1,8 @@ // Own simplified consola version +// Consola doesn't work on IE9 + +type LogCommand = 'info' | 'log' | 'error' | 'warn' +const logs: LogCommand[] = ['info', 'log', 'error', 'warn'] export default { tag: '', @@ -12,4 +16,11 @@ export default { if (this.tag) console.info(`[${this.tag}]`, ...args) else console.info(...args) }, + + mockTypes(spyCreator: () => any): void { + for (const log of logs) { + // @ts-ignore + this[log] = spyCreator() + } + }, } diff --git a/src/errors.ts b/src/errors.ts index df49b9d6..28673583 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,16 +1,80 @@ import { RouteLocationNormalized, RouteLocation } from './types' -export class NoRouteMatchError extends Error { +// we could use symbols, but this is for IE9 only and there is +// not Symbol support anyway +const isRouterError = '__RouterError' + +/** + * Generic Error coming from the Router. + */ +export class RouterError extends Error { + protected __proto__: any + // @ts-ignore for IE inheritance support + private [isRouterError] = true + + /** + * Creates a Router specific Error + * + * @param message Error Message + */ + constructor(message: string) { + super(message) + + // restore prototype chain + const actualProto = new.target.prototype + + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto) + } else { + this.__proto__ = actualProto + } + } + + static is(error: Error): error is RouterError { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isRouterError] + } else { + return error instanceof RouterError + } + } + + get name() { + return this.constructor.name + } +} + +const isNoRouteMatchError = '__NoRouteMatchError' +export class NoRouteMatchError extends RouterError { + // @ts-ignore for IE inheritance support + private [isNoRouteMatchError] = true + constructor(currentLocation: any, location: any) { super('No match for ' + JSON.stringify({ ...currentLocation, ...location })) - Object.setPrototypeOf(this, new.target.prototype) + } + + static is(error: Error): error is NoRouteMatchError { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isNoRouteMatchError] + } else { + return error instanceof NoRouteMatchError + } } } +const isInvalidRouteMatch = '__InvalidRouteMatch' /** * Error used when the matcher fails to resolve a location */ -export class InvalidRouteMatch extends Error { +export class InvalidRouteMatch extends RouterError { + // @ts-ignore for IE inheritance support + private [isNoRouteMatchError] = true + constructor(location: any) { // TODO: improve the error to include currentLocation and use it for more cases super( @@ -18,15 +82,29 @@ export class InvalidRouteMatch extends Error { location )}\nUse the function redirect and explicitely provide a name` ) - Object.setPrototypeOf(this, new.target.prototype) + } + + static is(error: Error): error is InvalidRouteMatch { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isInvalidRouteMatch] + } else { + return error instanceof InvalidRouteMatch + } } } +const isNavigationGuardRedirect = '__NavigationGuardRedirect' /** * Error used when rejecting a navigation because of a redirection. Contains * information about where we where trying to go and where we are going instead */ -export class NavigationGuardRedirect extends Error { +export class NavigationGuardRedirect extends RouterError { + // @ts-ignore for IE inheritance support + private [isNoRouteMatchError] = true + to: RouteLocation from: RouteLocationNormalized // TODO: refactor order of argumnets @@ -37,50 +115,85 @@ export class NavigationGuardRedirect extends Error { to )}" via a navigation guard` ) - Object.setPrototypeOf(this, new.target.prototype) this.from = from this.to = to } + + static is(error: Error): error is NavigationGuardRedirect { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isNavigationGuardRedirect] + } else { + return error instanceof NavigationGuardRedirect + } + } } +const isNavigationAborted = '__NavigationAborted' /** * Navigation aborted by next(false) */ -export class NavigationAborted extends Error { +export class NavigationAborted extends RouterError { + // @ts-ignore for IE inheritance support + private [isNavigationAborted] = true + to: RouteLocationNormalized from: RouteLocationNormalized constructor(to: RouteLocationNormalized, from: RouteLocationNormalized) { super( - `Navigation aborted from "${from.fullPath}" to "${ - to.fullPath - }" via a navigation guard` + `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard` ) - Object.setPrototypeOf(this, new.target.prototype) this.from = from this.to = to } + + static is(error: Error): error is NavigationAborted { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isNavigationAborted] + } else { + return error instanceof NavigationAborted + } + } } +const isNavigationCancelled = '__NavigationCancelled' /** * Navigation canceled by the user by pushing/replacing a new location * TODO: is the name good? */ -export class NavigationCancelled extends Error { +// @ts-ignore RouterError is a constructor +export class NavigationCancelled extends RouterError { + // @ts-ignore for IE inheritance support + private [isNavigationCancelled] = true + to: RouteLocationNormalized from: RouteLocationNormalized constructor(to: RouteLocationNormalized, from: RouteLocationNormalized) { super( - `Navigation cancelled from "${from.fullPath}" to "${ - to.fullPath - }" with a new \`push\` or \`replace\`` + `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new \`push\` or \`replace\`` ) - Object.setPrototypeOf(this, new.target.prototype) this.from = from this.to = to } + + static is(error: Error): error is NavigationCancelled { + // only IE9 seems to break the inheritance chain + // and set Error as the name + if (error.name === 'Error') { + // @ts-ignore for IE inheritance suport + return error[isNavigationCancelled] + } else { + return error instanceof NavigationCancelled + } + } } function stringifyRoute(to: RouteLocation): string { diff --git a/src/history/html5.ts b/src/history/html5.ts index 6933bad6..0fb5a702 100644 --- a/src/history/html5.ts +++ b/src/history/html5.ts @@ -1,4 +1,4 @@ -import consola from 'consola' +import consola from '../consola' import { BaseHistory, HistoryLocationNormalized, HistoryLocation } from './base' import { NavigationCallback, HistoryState, NavigationDirection } from './base' diff --git a/src/index.ts b/src/index.ts index bfc1bc0c..11a399bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ import { Router, RouterOptions } from './router' import { HTML5History } from './history/html5' +import { HashHistory } from './history/hash' +import { AbstractHistory } from './history/abstract' +import { BaseHistory } from './history/base' import { PluginFunction, VueConstructor } from 'vue' import View from './components/View' import Link from './components/Link' @@ -60,7 +63,14 @@ const plugin: PluginFunction = Vue => { strats.created } -export { Router, HTML5History, plugin } +export { + Router, + HTML5History, + HashHistory, + AbstractHistory, + BaseHistory, + plugin, +} export default class VueRouter extends Router { static install = plugin diff --git a/src/router.ts b/src/router.ts index 68877714..f6c69ba5 100644 --- a/src/router.ts +++ b/src/router.ts @@ -78,7 +78,7 @@ export class Router { } this.updateReactiveRoute() } catch (error) { - if (error instanceof NavigationGuardRedirect) { + if (NavigationGuardRedirect.is(error)) { // TODO: refactor the duplication of new NavigationCancelled by // checking instanceof NavigationError (it's another TODO) // a more recent navigation took place @@ -93,7 +93,7 @@ export class Router { // the error is already handled by router.push // we just want to avoid logging the error this.push(error.to).catch(() => {}) - } else if (error instanceof NavigationAborted) { + } else if (NavigationAborted.is(error)) { // TODO: test on different browsers ensure consistent behavior // TODO: this doesn't work if the user directly calls window.history.go(-n) with n > 1 // We can override the go method to retrieve the number but not sure if all browsers allow that @@ -234,7 +234,7 @@ export class Router { try { await this.navigate(toLocation, this.currentRoute) } catch (error) { - if (error instanceof NavigationGuardRedirect) { + if (NavigationGuardRedirect.is(error)) { // push was called while waiting in guards if (this.pendingLocation !== toLocation) { // TODO: trigger onError as well @@ -454,7 +454,7 @@ export class Router { try { await this.navigate(toLocation, this.currentRoute) } catch (error) { - if (error instanceof NavigationGuardRedirect) { + if (NavigationGuardRedirect.is(error)) { // push was called while waiting in guards if (this.pendingLocation !== toLocation) { // TODO: trigger onError as well diff --git a/yarn.lock b/yarn.lock index b01e5c3f..afce9923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,10 +393,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.0.tgz#b0df8d6ef9b5001b2be3a94d909ce3c29a80f9e1" integrity sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng== -"@types/node@^12.0.8": - version "12.0.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031" - integrity sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ== +"@types/node@^12.0.10": + version "12.0.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.12.tgz#cc791b402360db1eaf7176479072f91ee6c6c7ca" + integrity sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ== "@types/resolve@0.0.8": version "0.0.8" @@ -5313,13 +5313,14 @@ rollup-plugin-replace@^2.2.0: magic-string "^0.25.2" rollup-pluginutils "^2.6.0" -rollup-plugin-terser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.0.0.tgz#ac50fdb703b580447a7e6b1692aeed515a6be8cf" - integrity sha512-W+jJ4opYnlmNyVW0vtRufs+EGf68BIJ7bnOazgz8mgz8pA9lUyrEifAhPs5y9M16wFeAyBGaRjKip4dnFBtXaw== +rollup-plugin-terser@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.1.0.tgz#82e637613d5af6c85ac4319b508b1c065d64075c" + integrity sha512-tvJweguo+f9T1SBcSmEMaArCUM07mIg61ArqPj3Fty9OdwTLCxBUBdxS3e1cU68Z1lXf52JBhbt3yRuQoHLtQg== dependencies: "@babel/code-frame" "^7.0.0" jest-worker "^24.6.0" + rollup-pluginutils "^2.8.1" serialize-javascript "^1.7.0" terser "^4.0.0" @@ -5348,13 +5349,13 @@ rollup-pluginutils@^2.6.0, rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^1.16.3: - version "1.16.3" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.16.3.tgz#9b8bcf31523efc83447a9624bd2fe6ca58caa1e7" - integrity sha512-iXINUUEk2NTZXE3GcUtLQt2cvfQsAUXBQ8AFsDK8tg7Wp5bwTKdZXPdzB2IJQwHpdUNfsIgYMAfajurh7SVTnA== +rollup@^1.16.6: + version "1.16.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.16.6.tgz#0d979e3edd14a27e5436a145c7f1fcef35e0083b" + integrity sha512-oM3iKkzPCq9Da95wCnNfS8YlNZjgCD5c/TceKnJIthI9FOeJqnO3PUr/C5Suv9Kjzh0iphKL02PLeja3A5AMIA== dependencies: "@types/estree" "0.0.39" - "@types/node" "^12.0.8" + "@types/node" "^12.0.10" acorn "^6.1.1" rsvp@^4.8.4: