diff --git a/__tests__/errors.spec.ts b/__tests__/errors.spec.ts index 5c48509fd..9ee941072 100644 --- a/__tests__/errors.spec.ts +++ b/__tests__/errors.spec.ts @@ -1,5 +1,5 @@ import { createRouter as newRouter, createMemoryHistory } from '../src' -import { NavigationAborted, NavigationGuardRedirect } from '../src/errors' +import { ErrorTypes } from '../src/errors' import { components, tick } from './utils' import { RouteRecord } from '../src/types' @@ -47,9 +47,11 @@ describe('Errors', () => { try { await router.push('/foo') } catch (err) { - expect(err).toBeInstanceOf(NavigationAborted) + expect(err.type).toBe(ErrorTypes.NAVIGATION_ABORTED) } - expect(onError).toHaveBeenCalledWith(expect.any(NavigationAborted)) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ type: ErrorTypes.NAVIGATION_ABORTED }) + ) }) it('triggers erros caused by new navigations of a next(redirect) trigered by history', async () => { @@ -69,12 +71,15 @@ describe('Errors', () => { expect(onError).toHaveBeenCalledTimes(2) expect(onError).toHaveBeenNthCalledWith( 1, - expect.any(NavigationGuardRedirect) + expect.objectContaining({ type: ErrorTypes.NAVIGATION_GUARD_REDIRECT }) ) expect(onError.mock.calls[0]).toMatchObject([ { to: { params: { p: '1' } }, from: { fullPath: '/p/0' } }, ]) - expect(onError).toHaveBeenNthCalledWith(2, expect.any(NavigationAborted)) + expect(onError).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ type: ErrorTypes.NAVIGATION_ABORTED }) + ) expect(onError.mock.calls[1]).toMatchObject([ { to: { params: { p: '1' } }, from: { params: { p: 'other' } } }, ]) diff --git a/__tests__/matcher/__snapshots__/resolve.spec.ts.snap b/__tests__/matcher/__snapshots__/resolve.spec.ts.snap index 4592f7d09..c9324fbe1 100644 --- a/__tests__/matcher/__snapshots__/resolve.spec.ts.snap +++ b/__tests__/matcher/__snapshots__/resolve.spec.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Router Matcher resolve LocationAsName throws if the named route does not exists 1`] = ` -[NoRouteMatchError: No match for -{"name":"Home"}] +[Error: No match for + {"name":"Home"}] `; exports[`Router Matcher resolve LocationAsRelative throws if the current named route does not exists 1`] = ` -[NoRouteMatchError: No match for -{"params":{"a":"foo"}} +[Error: No match for + {"params":{"a":"foo"}} while being at {"name":"home","params":{},"path":"/","meta":{}}] `; diff --git a/__tests__/router.spec.ts b/__tests__/router.spec.ts index e6a9a966f..ea69c117c 100644 --- a/__tests__/router.spec.ts +++ b/__tests__/router.spec.ts @@ -1,6 +1,6 @@ import fakePromise from 'faked-promise' import { createRouter, createMemoryHistory, createWebHistory } from '../src' -import { NavigationCancelled } from '../src/errors' +import { ErrorTypes } from '../src/errors' import { createDom, components, tick } from './utils' import { RouteRecord, @@ -225,7 +225,7 @@ describe('Router', () => { try { await pA } catch (err) { - expect(err).toBeInstanceOf(NavigationCancelled) + expect(err.type).toBe(ErrorTypes.NAVIGATION_CANCELLED) } expect(router.currentRoute.value.fullPath).toBe('/p/b') } diff --git a/package.json b/package.json index 54fbafad2..4450efe86 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "4.0.0-alpha.3", "main": "dist/vue-router.cjs.js", "browser": "dist/vue-router.esm.js", - "unpkg": "dist/vue-router.js", + "unpkg": "dist/vue-router.global.js", "module": "dist/vue-router.esm-bundler.js", "typings": "dist/vue-router.d.ts", "sideEffects": false, diff --git a/rollup.config.js b/rollup.config.js index 5a7d111da..c0fb2cc3a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,10 +1,11 @@ +import path from 'path' +import ts from 'rollup-plugin-typescript2' import replace from '@rollup/plugin-replace' import resolve from '@rollup/plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' -import ts from 'rollup-plugin-typescript2' -import alias from '@rollup/plugin-alias' -import { terser } from 'rollup-plugin-terser' -import pkg from './package.json' + +const pkg = require('./package.json') +const name = pkg.name const banner = `/*! * ${pkg.name} v${pkg.version} @@ -12,108 +13,176 @@ const banner = `/*! * @license MIT */` -const exportName = 'VueRouter' - -function createEntry( - { - format, // Rollup format (iife, umd, cjs, es) - external = ['vue', '@vue/reactivity', '@vue/runtime-core'], // Rollup external option - input = 'src/index.ts', // entry point - env = 'development', // NODE_ENV variable - minify = false, - isBrowser = false, // produce a browser module version or not - } = { - input: 'src/index.ts', - env: 'development', - minify: false, - isBrowser: false, - } -) { - // force production mode when minifying - if (minify) env = 'production' - const isProductionBuild = - process.env.__DEV__ === 'false' || env === 'production' +// ensure TS checks only once for each build +let hasTSChecked = false - const config = { - input, - plugins: [ - replace({ - __VERSION__: JSON.stringify(pkg.version), - __DEV__: - (format === 'es' && !isBrowser) || format === 'cjs' - ? // preserve to be handled by bundlers - `process.env.NODE_ENV !== 'production'` - : // hard coded dev/prod builds - !isProductionBuild, - }), - alias({ - resolve: ['ts'], - }), - ], - output: { - banner, - file: 'dist/vue-router.other.js', - format, - globals: { - '@vue/reactivity': 'Vue', - '@vue/runtime-core': 'Vue', - vue: 'Vue', - }, - }, - } +const outputConfigs = { + // each file name has the format: `dist/${name}.${format}.js` + // format being a key of this object + 'esm-bundler': { + file: pkg.module, + format: `es`, + }, + cjs: { + file: pkg.main, + format: `cjs`, + }, + global: { + file: pkg.unpkg, + format: `iife`, + }, + esm: { + file: pkg.browser, + format: `es`, + }, +} - if (format === 'iife') { - // config.input = 'src/entries/iife.ts' - config.output.file = pkg.unpkg - config.output.name = exportName - } else if (format === 'es') { - config.output.file = isBrowser ? pkg.browser : pkg.module - } else if (format === 'cjs') { - config.output.file = 'dist/vue-router.cjs.js' +const allFormats = Object.keys(outputConfigs) +// in vue-router there are not that many +const packageFormats = allFormats +const packageConfigs = packageFormats.map(format => + createConfig(format, outputConfigs[format]) +) + +// only add the production ready if we are bundling the options +packageFormats.forEach(format => { + if (format === 'cjs') { + packageConfigs.push(createProductionConfig(format)) + } else if (format === 'global') { + packageConfigs.push(createMinifiedConfig(format)) } +}) + +export default packageConfigs - if (!external) { - config.plugins.push(resolve(), commonjs()) - } else { - config.external = external +function createConfig(format, output, plugins = []) { + if (!output) { + console.log(require('chalk').yellow(`invalid format: "${format}"`)) + process.exit(1) } - config.plugins.push( - ts({ - // only check once, during the es version with browser (it includes external libs) - check: format === 'es' && isBrowser && !minify, - tsconfigOverride: { - compilerOptions: { - // 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' ? 'es5' : 'esnext', - }, + output.sourcemap = true + output.banner = banner + output.externalLiveBindings = false + output.globals = { vue: 'Vue' } + + const isProductionBuild = /\.prod\.js$/.test(output.file) + const isGlobalBuild = format === 'global' + const isRawESMBuild = format === 'esm' + const isNodeBuild = format === 'cjs' + const isBundlerESMBuild = /esm-bundler/.test(format) + + if (isGlobalBuild) output.name = 'VueRouter' + + const shouldEmitDeclarations = !hasTSChecked + + const tsPlugin = ts({ + check: !hasTSChecked, + tsconfig: path.resolve(__dirname, 'tsconfig.json'), + cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'), + tsconfigOverride: { + compilerOptions: { + sourceMap: output.sourcemap, + declaration: shouldEmitDeclarations, + declarationMap: shouldEmitDeclarations, }, - }) - ) + exclude: ['__tests__', 'test-dts'], + }, + }) + // we only need to check TS and generate declarations once for each build. + // it also seems to run into weird issues when checking multiple times + // during a single build. + hasTSChecked = true - if (minify) { - config.plugins.push( - terser({ - module: format === 'es', - // output: { - // preamble: banner, - // }, - }) - ) - config.output.file = config.output.file.replace(/\.js$/i, '.min.js') + const external = ['vue'] + + const nodePlugins = [resolve(), commonjs()] + + return { + input: `src/index.ts`, + // Global and Browser ESM builds inlines everything so that they can be + // used alone. + external, + plugins: [ + tsPlugin, + createReplacePlugin( + isProductionBuild, + isBundlerESMBuild, + // isBrowserBuild? + isGlobalBuild || isRawESMBuild || isBundlerESMBuild, + isGlobalBuild, + isNodeBuild + ), + ...nodePlugins, + ...plugins, + ], + output, + // onwarn: (msg, warn) => { + // if (!/Circular/.test(msg)) { + // warn(msg) + // } + // }, } +} - return config +function createReplacePlugin( + isProduction, + isBundlerESMBuild, + isBrowserBuild, + isGlobalBuild, + isNodeBuild +) { + const replacements = { + __COMMIT__: `"${process.env.COMMIT}"`, + __VERSION__: `"${pkg.version}"`, + __DEV__: isBundlerESMBuild + ? // preserve to be handled by bundlers + `(process.env.NODE_ENV !== 'production')` + : // hard coded dev/prod builds + !isProduction, + // this is only used during tests + __TEST__: isBundlerESMBuild ? `(process.env.NODE_ENV === 'test')` : false, + // If the build is expected to run directly in the browser (global / esm builds) + __BROWSER__: isBrowserBuild, + // is targeting bundlers? + __BUNDLER__: isBundlerESMBuild, + __GLOBAL__: isGlobalBuild, + // is targeting Node (SSR)? + __NODE_JS__: isNodeBuild, + } + // allow inline overrides like + //__RUNTIME_COMPILE__=true yarn build + Object.keys(replacements).forEach(key => { + if (key in process.env) { + replacements[key] = process.env[key] + } + }) + return replace(replacements) +} + +function createProductionConfig(format) { + return createConfig(format, { + file: `dist/${name}.${format}.prod.js`, + format: outputConfigs[format].format, + }) } -export default [ - // browser-friendly UMD build - createEntry({ format: 'iife' }), - createEntry({ format: 'iife', minify: true }), - createEntry({ format: 'cjs' }), - // TODO: prod vs env - createEntry({ format: 'es' }), - createEntry({ format: 'es', isBrowser: true }), -] +function createMinifiedConfig(format) { + const { terser } = require('rollup-plugin-terser') + return createConfig( + format, + { + file: `dist/${name}.${format}.prod.js`, + format: outputConfigs[format].format, + }, + [ + terser({ + module: /^esm/.test(format), + compress: { + ecma: 2015, + pure_getters: true, + }, + }), + ] + ) +} diff --git a/src/errors.ts b/src/errors.ts index 07fa4e41f..69d855a79 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,227 +1,97 @@ import { - RouteLocationNormalized, - RouteLocation, - MatcherLocationNormalized, MatcherLocation, + MatcherLocationNormalized, + RouteLocation, + RouteLocationNormalized, } from './types' -// 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 support - return error[isRouterError] - } else { - return error instanceof RouterError - } - } - - get name() { - return this.constructor.name - } +export const enum ErrorTypes { + MATCHER_NOT_FOUND, + NAVIGATION_GUARD_REDIRECT, + NAVIGATION_ABORTED, + NAVIGATION_CANCELLED, + // Using string enums because error codes are exposed to developers + // and number enums could collide with other error codes in runtime + // MATCHER_NOT_FOUND = 'MATCHER_NOT_FOUND', + // NAVIGATION_GUARD_REDIRECT = 'NAVIGATION_GUARD_REDIRECT', + // NAVIGATION_ABORTED = 'NAVIGATION_ABORTED', + // NAVIGATION_CANCELLED = 'NAVIGATION_CANCELLED', } -const isNoRouteMatchError = '__NoRouteMatchError' -export class NoRouteMatchError extends RouterError { - // @ts-ignore for IE inheritance support - private [isNoRouteMatchError] = true - - constructor( - location: MatcherLocation, - currentLocation?: MatcherLocationNormalized - ) { - super( - 'No match for\n' + - JSON.stringify(location) + - (currentLocation - ? '\nwhile being at\n' + JSON.stringify(currentLocation) - : '') - ) - } - - 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 support - return error[isNoRouteMatchError] - } else { - return error instanceof NoRouteMatchError - } - } +interface RouterErrorBase extends Error { + type: ErrorTypes } -const isInvalidRouteMatch = '__InvalidRouteMatch' -/** - * Error used when the matcher fails to resolve a location - */ -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( - `Cannot redirect using a relative location:\n${stringifyRoute( - location - )}\nUse the function redirect and explicitly provide a name` - ) - } - - 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 support - return error[isInvalidRouteMatch] - } else { - return error instanceof InvalidRouteMatch - } - } +export interface MatcherError extends RouterErrorBase { + type: ErrorTypes.MATCHER_NOT_FOUND + location: MatcherLocation + currentLocation?: MatcherLocationNormalized } -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 RouterError { - // @ts-ignore for IE inheritance support - private [isNoRouteMatchError] = true - - to: RouteLocation +export interface NavigationError extends RouterErrorBase { + type: ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED from: RouteLocationNormalized - // TODO: refactor order of arguments - // TODO: refactor into parent class NavigationError - constructor(from: RouteLocationNormalized, to: RouteLocation) { - super( - `Redirected from "${from.fullPath}" to "${stringifyRoute( - to - )}" via a navigation guard` - ) - - 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 support - return error[isNavigationGuardRedirect] - } else { - return error instanceof NavigationGuardRedirect - } - } -} - -const isNavigationAborted = '__NavigationAborted' -/** - * Navigation aborted by next(false) - */ -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` - ) - - 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 support - 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? - */ -// @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\`` - ) +export interface NavigationRedirectError + extends Omit { + type: ErrorTypes.NAVIGATION_GUARD_REDIRECT + to: RouteLocation +} - this.from = from - this.to = to - } +// DEV only debug messages +const ErrorTypeMessages = { + [ErrorTypes.MATCHER_NOT_FOUND]({ location, currentLocation }: MatcherError) { + return `No match for\n ${JSON.stringify(location)}${ + currentLocation + ? '\nwhile being at\n' + JSON.stringify(currentLocation) + : '' + }` + }, + [ErrorTypes.NAVIGATION_GUARD_REDIRECT]({ + from, + to, + }: NavigationRedirectError) { + return `Redirected from "${from.fullPath}" to "${stringifyRoute( + to + )}" via a navigation guard` + }, + [ErrorTypes.NAVIGATION_ABORTED]({ from, to }: NavigationError) { + return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard` + }, + [ErrorTypes.NAVIGATION_CANCELLED]({ from, to }: NavigationError) { + return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new \`push\` or \`replace\`` + }, +} - 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 support - return error[isNavigationCancelled] - } else { - return error instanceof NavigationCancelled - } +// Possible internal errors +type RouterError = NavigationError | NavigationRedirectError | MatcherError +// Public errors, TBD +// export type PublicRouterError = NavigationError + +export function createRouterError( + type: E['type'], + params: Omit +): E { + if (__DEV__ || !__BROWSER__) { + return Object.assign( + new Error(ErrorTypeMessages[type](params as any)), + { type }, + params + ) as E + } else { + return Object.assign(new Error(), { type }, params) as E } } -const propertiesToLog: (keyof RouteLocationNormalized)[] = [ - 'params', - 'query', - 'hash', -] +const propertiesToLog = ['params', 'query', 'hash'] as const function stringifyRoute(to: RouteLocation): string { if (typeof to === 'string') return to if ('path' in to) return to.path - const location: Partial = {} + const location = {} as Record for (const key of propertiesToLog) { - // @ts-ignore if (key in to) location[key] = to[key] } return JSON.stringify(location, null, 2) diff --git a/src/global.d.ts b/src/global.d.ts index ca93dc0d6..d276eaf92 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,2 +1,3 @@ // Global compile-time constants declare var __DEV__: boolean +declare var __BROWSER__: boolean diff --git a/src/index.ts b/src/index.ts index e5058595d..4bccbb7b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,11 +4,10 @@ import createWebHashHistory from './history/hash' export { RouteLocationNormalized, - START_LOCATION_NORMALIZED as START_LOCATION, - // needed for types, should probably be removed by changing the RouteLocationOptions, + START_LOCATION_NORMALIZED as START_LOCATION, } from './types' -export { createRouter, Router } from './router' +export { createRouter, Router, RouterOptions } from './router' export { onBeforeRouteLeave } from './navigationGuards' export { useRoute, useRouter } from './injectKeys' diff --git a/src/matcher/index.ts b/src/matcher/index.ts index 002dec0c5..e4fe9642c 100644 --- a/src/matcher/index.ts +++ b/src/matcher/index.ts @@ -4,7 +4,7 @@ import { MatcherLocationNormalized, ListenerRemover, } from '../types' -import { NoRouteMatchError } from '../errors' +import { createRouterError, ErrorTypes, MatcherError } from '../errors' import { createRouteRecordMatcher, RouteRecordMatcher } from './path-matcher' import { RouteRecordNormalized } from './types' import { @@ -188,7 +188,10 @@ export function createRouterMatcher( if ('name' in location && location.name) { matcher = matcherMap.get(location.name) - if (!matcher) throw new NoRouteMatchError(location) + if (!matcher) + throw createRouterError(ErrorTypes.MATCHER_NOT_FOUND, { + location, + }) name = matcher.record.name // TODO: merge params with current location. Should this be done by name. I think there should be some kind of relationship between the records like children of a parent should keep parent props but not the rest @@ -214,7 +217,11 @@ export function createRouterMatcher( matcher = currentLocation.name ? matcherMap.get(currentLocation.name) : matchers.find(m => m.re.test(currentLocation.path)) - if (!matcher) throw new NoRouteMatchError(location, currentLocation) + if (!matcher) + throw createRouterError(ErrorTypes.MATCHER_NOT_FOUND, { + location, + currentLocation, + }) name = matcher.record.name params = location.params || currentLocation.params path = matcher.stringify(params) diff --git a/src/router.ts b/src/router.ts index 4c2f87916..54cd0b568 100644 --- a/src/router.ts +++ b/src/router.ts @@ -19,11 +19,7 @@ import { scrollToPosition, } from './utils/scroll' import { createRouterMatcher } from './matcher' -import { - NavigationCancelled, - NavigationGuardRedirect, - NavigationAborted, -} from './errors' +import { createRouterError, ErrorTypes, NavigationError } from './errors' import { extractComponentsGuards, guardToPromiseFn, @@ -218,19 +214,23 @@ export function createRouter({ try { await navigate(toLocation, from) } catch (error) { - if (NavigationGuardRedirect.is(error)) { - // push was called while waiting in guards - if (pendingLocation !== toLocation) { - triggerError(new NavigationCancelled(toLocation, from)) - } + // push was called while waiting in guards + // TODO: write tests + if (pendingLocation !== toLocation) { + triggerError( + createRouterError(ErrorTypes.NAVIGATION_CANCELLED, { + from, + to: toLocation, + }) + ) + } + + if (error.type === ErrorTypes.NAVIGATION_GUARD_REDIRECT) { // preserve the original redirectedFrom if any return pushWithRedirect(error.to, redirectedFrom || toLocation) - } else { - // TODO: write tests - if (pendingLocation !== toLocation) { - triggerError(new NavigationCancelled(toLocation, from)) - } } + + // unkwnown error triggerError(error) } @@ -345,7 +345,13 @@ export function createRouter({ ) { // a more recent navigation took place if (pendingLocation !== toLocation) { - return triggerError(new NavigationCancelled(toLocation, from), isPush) + return triggerError( + createRouterError(ErrorTypes.NAVIGATION_CANCELLED, { + from, + to: toLocation, + }), + isPush + ) } // remove registered guards from removed matched records @@ -397,19 +403,28 @@ export function createRouter({ false ) } catch (error) { - if (NavigationGuardRedirect.is(error)) { + if (error.type === ErrorTypes.NAVIGATION_GUARD_REDIRECT) { // TODO: refactor the duplication of new NavigationCancelled by // checking instanceof NavigationError (it's another TODO) // a more recent navigation took place if (pendingLocation !== toLocation) { - return triggerError(new NavigationCancelled(toLocation, from), false) + return triggerError( + createRouterError( + ErrorTypes.NAVIGATION_CANCELLED, + { + from, + to: toLocation, + } + ), + false + ) } triggerError(error, false) // the error is already handled by router.push // we just want to avoid logging the error pushWithRedirect(error.to, toLocation).catch(() => {}) - } else if (NavigationAborted.is(error)) { + } else if (error.type === ErrorTypes.NAVIGATION_ABORTED) { console.log('Cancelled, going to', -info.distance) // TODO: test on different browsers ensure consistent behavior history.go(-info.distance, false) diff --git a/src/utils/guardToPromiseFn.ts b/src/utils/guardToPromiseFn.ts index 29bf6e6fd..7d1f7c9ff 100644 --- a/src/utils/guardToPromiseFn.ts +++ b/src/utils/guardToPromiseFn.ts @@ -8,7 +8,12 @@ import { } from '../types' import { isRouteLocation } from '../types' -import { NavigationGuardRedirect, NavigationAborted } from '../errors' +import { + createRouterError, + ErrorTypes, + NavigationError, + NavigationRedirectError, +} from '../errors' export function guardToPromiseFn( guard: NavigationGuard, @@ -21,9 +26,23 @@ export function guardToPromiseFn( const next: NavigationGuardCallback = ( valid?: boolean | RouteLocation | NavigationGuardNextCallback ) => { - if (valid === false) reject(new NavigationAborted(to, from)) + if (valid === false) + reject( + createRouterError(ErrorTypes.NAVIGATION_ABORTED, { + from, + to, + }) + ) else if (isRouteLocation(valid)) { - reject(new NavigationGuardRedirect(to, valid)) + reject( + createRouterError( + ErrorTypes.NAVIGATION_GUARD_REDIRECT, + { + from: to, + to: valid, + } + ) + ) } else if (!valid || valid === true) { resolve() } else { diff --git a/tsconfig.json b/tsconfig.json index e9fd80fe1..fd5c1f060 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,31 @@ { "include": ["src/global.d.ts", "src/**/*.ts", "__tests__/**/*.ts"], "compilerOptions": { - "target": "esnext", - "module": "commonjs", - // "lib": ["es2017.object"] /* Specify library files to be included in the compilation. */, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist", - "removeComments": false, + "baseUrl": ".", + "rootDir": ".", + "outDir": "dist", + "sourceMap": false, "noEmit": true, - "strict": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "allowJs": false, "noUnusedLocals": true, - // "noUnusedParameters": true, + "strictNullChecks": true, + "noImplicitAny": true, + "noImplicitThis": true, "noImplicitReturns": true, + "strict": true, + "isolatedModules": false, - "moduleResolution": "node", - "esModuleInterop": true + "experimentalDecorators": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "removeComments": false, + "jsx": "preserve", + "lib": ["esnext", "dom"], + "types": ["jest", "node"] } }