/**
* @jest-environment jsdom
*/
-import RouterView from '../src/components/View'
-import { components } from './utils'
-import {
- START_LOCATION_NORMALIZED,
- RouteLocationNormalized,
-} from '../src/types'
+import { View as RouterView } from '../src/components/View'
+import { components, RouteLocationNormalizedLoose } from './utils'
+import { START_LOCATION_NORMALIZED } from '../src/types'
import { ref, markNonReactive } from 'vue'
import { mount, tick } from './mount'
-const routes: Record<string, RouteLocationNormalized> = {
+const routes: Record<string, RouteLocationNormalizedLoose> = {
root: {
fullPath: '/',
name: undefined,
}
describe('RouterView', () => {
- function factory(route: RouteLocationNormalized, props: any = {}) {
+ function factory(route: RouteLocationNormalizedLoose, props: any = {}) {
const router = {
currentRoute: ref(markNonReactive(route)),
}
})
it('displays nothing when route is unmatched', () => {
- const { el } = factory(START_LOCATION_NORMALIZED)
+ const { el } = factory(START_LOCATION_NORMALIZED as any)
// NOTE: I wonder if this will stay stable in future releases
expect(el.childElementCount).toBe(0)
})
MatcherLocation,
MatcherLocationNormalized,
MatcherLocationRedirect,
- RouteRecordMultipleViews,
} from '../../src/types'
-import { normalizeRouteRecord } from '../utils'
+import { normalizeRouteRecord, MatcherLocationNormalizedLoose } from '../utils'
// @ts-ignore
const component: RouteComponent = null
// for normalized records
const components = { default: component }
-interface MatcherLocationNormalizedLoose {
- name: string
- path: string
- // record?
- params: any
- redirectedFrom?: Partial<MatcherLocationNormalized>
- meta: any
- matched: Partial<RouteRecordViewLoose>[]
-}
-
-type RouteRecordViewLoose = Pick<
- RouteRecordMultipleViews,
- 'path' | 'name' | 'components' | 'children' | 'meta' | 'beforeEnter'
->
-
describe('Router Matcher', () => {
describe('resolve', () => {
function assertRecordMatch(
import { JSDOM, ConstructorOptions } from 'jsdom'
-import { NavigationGuard, RouteRecord } from '../src/types'
-import { h, resolveComponent } from '@vue/runtime-core'
+import {
+ NavigationGuard,
+ RouteRecord,
+ RouteRecordMultipleViews,
+ MatcherLocationNormalized,
+ RouteLocationNormalized,
+} from '../src/types'
+import { h, resolveComponent } from 'vue'
import { RouteRecordMatched } from '../src/matcher/types'
export const tick = (time?: number) =>
export type NAVIGATION_METHOD = 'push' | 'replace'
export const NAVIGATION_TYPES: NAVIGATION_METHOD[] = ['push', 'replace']
+export interface RouteRecordViewLoose
+ extends Pick<
+ RouteRecordMultipleViews,
+ 'path' | 'name' | 'components' | 'children' | 'meta' | 'beforeEnter'
+ > {
+ leaveGuards?: any
+}
+
+// @ts-ignore we are intentionally overriding the type
+export interface RouteLocationNormalizedLoose extends RouteLocationNormalized {
+ name: string | undefined
+ path: string
+ // record?
+ params: any
+ redirectedFrom?: Partial<MatcherLocationNormalized>
+ meta: any
+ matched: Partial<RouteRecordViewLoose>[]
+}
+
+export interface MatcherLocationNormalizedLoose {
+ name: string
+ path: string
+ // record?
+ params: any
+ redirectedFrom?: Partial<MatcherLocationNormalized>
+ meta: any
+ matched: Partial<RouteRecordViewLoose>[]
+}
+
declare global {
namespace NodeJS {
interface Global {
<script>
// @ts-check
import { defineComponent } from 'vue'
-import { onRouteLeave } from '../../src'
+import { onBeforeRouteLeave } from '../../src'
export default defineComponent({
name: 'GuardedWithLeave',
setup() {
console.log('setup in cant leave')
- onRouteLeave(function(to, from, next) {
+ onBeforeRouteLeave(function(to, from, next) {
if (window.confirm()) next()
else {
// @ts-ignore
-import { defineComponent, h, PropType, inject } from '@vue/runtime-core'
-import { computed, reactive, isRef, Ref } from '@vue/reactivity'
+import {
+ defineComponent,
+ h,
+ PropType,
+ inject,
+ computed,
+ reactive,
+ isRef,
+ Ref,
+} from 'vue'
import { RouteLocation } from '../types'
interface UseLinkProps {
PropType,
computed,
Component,
-} from '@vue/runtime-core'
+ InjectionKey,
+ Ref,
+} from 'vue'
+import { RouteRecordMatched } from '../matcher/types'
-const View = defineComponent({
+// TODO: make it work with no symbols too for IE
+export const matchedRouteKey = Symbol() as InjectionKey<Ref<RouteRecordMatched>>
+
+export const View = defineComponent({
name: 'RouterView',
props: {
name: {
() => matchedRoute.value && matchedRoute.value.components[props.name]
)
- provide('matchedRoute', matchedRoute)
+ provide(matchedRouteKey, matchedRoute)
return () => {
return ViewComponent.value
}
},
})
-
-export default View
import { createRouter, Router } from './router'
-import { App, Ref, inject, getCurrentInstance } from '@vue/runtime-core'
+import { App, Ref, InjectionKey } from 'vue'
import createHistory from './history/html5'
import createMemoryHistory from './history/memory'
import createHashHistory from './history/hash'
-import View from './components/View'
+import { View } from './components/View'
import Link from './components/Link'
import {
RouteLocationNormalized,
START_LOCATION_NORMALIZED as START_LOCATION,
- NavigationGuard,
} from './types'
-import { RouteRecordNormalized } from './matcher/types'
+import { onBeforeRouteLeave } from './navigationGuards'
-declare module '@vue/runtime-core' {
- function inject(name: 'router'): Router
- function inject(name: 'route'): Ref<RouteLocationNormalized>
- function inject(name: 'matchedRoute'): Ref<RouteRecordNormalized>
-}
-
-// @ts-ignore: we are not importing it so it complains
-declare module '@vue/runtime-dom' {
- function inject(name: 'router'): Router
- function inject(name: 'route'): RouteLocationNormalized
-}
-
-// @ts-ignore: we are not importing it so it complains
declare module 'vue' {
- function inject<T>(name: string, defaultValue: T): T
+ function inject<T>(key: InjectionKey<T> | string): T | undefined
+ function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
+ function inject(key: InjectionKey<any> | string, defaultValue?: unknown): any
function inject(name: 'router'): Router
- function inject(name: 'route'): RouteLocationNormalized
+ function inject(name: 'route'): Ref<RouteLocationNormalized>
}
export function RouterPlugin(app: App, router: Router) {
app.provide('route', router.currentRoute)
}
-function onRouteLeave(leaveGuard: NavigationGuard) {
- const matched = inject('matchedRoute').value
- if (!matched) {
- console.log('no matched record')
- return
- }
-
- matched.leaveGuards.push(leaveGuard.bind(getCurrentInstance()!.proxy))
-}
-
export {
createHistory,
createMemoryHistory,
RouteLocationNormalized,
Router,
START_LOCATION,
- onRouteLeave,
+ onBeforeRouteLeave,
}
NavigationGuard,
} from '../types'
-interface RouteRecordNormalizedCommon {
+export interface RouteRecordNormalizedCommon {
leaveGuards: NavigationGuard[]
}
--- /dev/null
+import { NavigationGuard } from './types'
+import { inject, getCurrentInstance, warn } from 'vue'
+import { matchedRouteKey } from './components/View'
+
+// TODO: why is this necessary if it's in global.d.ts, included in tsconfig.json
+declare var __DEV__: boolean
+
+export function onBeforeRouteLeave(leaveGuard: NavigationGuard) {
+ const instance = getCurrentInstance()
+ if (!instance) {
+ __DEV__ &&
+ warn('onRouteLeave must be called at the top of a setup function')
+ return
+ }
+
+ const matched = inject(matchedRouteKey, {}).value
+
+ if (!matched) {
+ __DEV__ &&
+ warn('onRouteLeave must be called at the top of a setup function')
+ return
+ }
+
+ matched.leaveGuards.push(leaveGuard.bind(instance!.proxy))
+}
RouteQueryAndHash,
Lazy,
TODO,
+ Immutable,
} from './types'
import {
RouterHistory,
import { useCallbacks } from './utils/callbacks'
import { encodeParam } from './utils/encoding'
import { decode } from './utils/encoding'
-import { ref, Ref, markNonReactive } from '@vue/reactivity'
-import { nextTick } from '@vue/runtime-core'
+import { ref, Ref, markNonReactive, nextTick } from 'vue'
import { RouteRecordMatched } from './matcher/types'
type ErrorHandler = (error: any) => any
export interface Router {
history: RouterHistory
- currentRoute: Ref<RouteLocationNormalized>
+ currentRoute: Ref<Immutable<RouteLocationNormalized>>
resolve(to: RouteLocation): RouteLocationNormalized
createHref(to: RouteLocationNormalized): string
const beforeGuards = useCallbacks<NavigationGuard>()
const afterGuards = useCallbacks<PostNavigationGuard>()
const currentRoute = ref<RouteLocationNormalized>(START_LOCATION_NORMALIZED)
- let pendingLocation: Readonly<RouteLocationNormalized> = START_LOCATION_NORMALIZED
+ let pendingLocation: Immutable<RouteLocationNormalized> = START_LOCATION_NORMALIZED
if (isClient && 'scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual'
import { HistoryQuery, RawHistoryQuery } from '../history/common'
import { PathParserOptions } from '../matcher/path-parser-ranker'
-import { markNonReactive } from '@vue/reactivity'
+import { markNonReactive } from 'vue'
import { RouteRecordMatched } from '../matcher/types'
// type Component = ComponentOptions<Vue> | typeof Vue | AsyncComponent
fullPath: string
query: HistoryQuery // the normalized version cannot have numbers
// TODO: do the same for params
- name: string | void
+ name: string | null | undefined
matched: RouteRecordMatched[] // non-enumerable
redirectedFrom?: RouteLocationNormalized
meta: Record<string | number | symbol, any>
| RouteRecordMultipleViews
| RouteRecordRedirect
-// TODO: this should probably be generate by ensureLocation
export const START_LOCATION_NORMALIZED: RouteLocationNormalized = markNonReactive(
{
path: '/',
template: resolve(__dirname, 'playground/index.html'),
}),
new webpack.DefinePlugin({
+ __DEV__: JSON.stringify(!env.prod),
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},