Skip to content

Commit

Permalink
feat: invoke guards with the right context
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Mar 18, 2020
1 parent 44486bc commit 7053413
Show file tree
Hide file tree
Showing 14 changed files with 108 additions and 35 deletions.
36 changes: 27 additions & 9 deletions __tests__/RouterView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const routes = createRoutes({
params: {},
hash: '',
meta: {},
matched: [{ components: { default: components.Home }, path: '/' }],
matched: [
{ components: { default: components.Home }, instances: {}, path: '/' },
],
},
foo: {
fullPath: '/foo',
Expand All @@ -34,7 +36,9 @@ const routes = createRoutes({
params: {},
hash: '',
meta: {},
matched: [{ components: { default: components.Foo }, path: '/foo' }],
matched: [
{ components: { default: components.Foo }, instances: {}, path: '/foo' },
],
},
nested: {
fullPath: '/a',
Expand All @@ -45,8 +49,8 @@ const routes = createRoutes({
hash: '',
meta: {},
matched: [
{ components: { default: components.Nested }, path: '/' },
{ components: { default: components.Foo }, path: 'a' },
{ components: { default: components.Nested }, instances: {}, path: '/' },
{ components: { default: components.Foo }, instances: {}, path: 'a' },
],
},
nestedNested: {
Expand All @@ -58,9 +62,9 @@ const routes = createRoutes({
hash: '',
meta: {},
matched: [
{ components: { default: components.Nested }, path: '/' },
{ components: { default: components.Nested }, path: 'a' },
{ components: { default: components.Foo }, path: 'b' },
{ components: { default: components.Nested }, instances: {}, path: '/' },
{ components: { default: components.Nested }, instances: {}, path: 'a' },
{ components: { default: components.Foo }, instances: {}, path: 'b' },
],
},
named: {
Expand All @@ -71,7 +75,9 @@ const routes = createRoutes({
params: {},
hash: '',
meta: {},
matched: [{ components: { foo: components.Foo }, path: '/' }],
matched: [
{ components: { foo: components.Foo }, instances: {}, path: '/' },
],
},
withParams: {
fullPath: '/users/1',
Expand All @@ -84,6 +90,8 @@ const routes = createRoutes({
matched: [
{
components: { default: components.User },

instances: {},
path: '/users/:id',
props: true,
},
Expand All @@ -100,6 +108,8 @@ const routes = createRoutes({
matched: [
{
components: { default: components.WithProps },

instances: {},
path: '/props/:id',
props: { id: 'foo', other: 'fixed' },
},
Expand All @@ -117,6 +127,8 @@ const routes = createRoutes({
matched: [
{
components: { default: components.WithProps },

instances: {},
path: '/props/:id',
props: to => ({ id: Number(to.params.id) * 2, other: to.query.q }),
},
Expand All @@ -129,7 +141,13 @@ describe('RouterView', () => {

function factory(route: RouteLocationNormalizedLoose, props: any = {}) {
const router = {
currentRoute: ref(markNonReactive({ ...route })),
currentRoute: ref(
markNonReactive({
...route,
// reset the instances everytime
matched: route.matched.map(match => ({ ...match, instances: {} })),
})
),
}

const { app, el } = mount(
Expand Down
5 changes: 5 additions & 0 deletions __tests__/guards/component-beforeRouteLeave.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ describe('beforeRouteLeave', () => {
await p.catch(err => {}) // catch the navigation abortion
expect(currentRoute.fullPath).toBe('/guard')
})

it.todo('invokes with the component context')
it.todo('invokes with the component context with named views')
it.todo('invokes with the component context with nested views')
it.todo('invokes with the component context with nested named views')
})
})
})
5 changes: 5 additions & 0 deletions __tests__/guards/component-beforeRouteUpdate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ describe('beforeRouteUpdate', () => {
await p
expect(router.currentRoute.value.fullPath).toBe('/guard/foo')
})

it.todo('invokes with the component context')
it.todo('invokes with the component context with named views')
it.todo('invokes with the component context with nested views')
it.todo('invokes with the component context with nested named views')
})
})
})
5 changes: 5 additions & 0 deletions __tests__/matcher/records.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('normalizeRouteRecord', () => {
aliasOf: undefined,
components: { default: {} },
leaveGuards: [],
instances: {},
meta: {},
name: undefined,
path: '/home',
Expand All @@ -34,6 +35,7 @@ describe('normalizeRouteRecord', () => {
children: [{ path: '/child' }],
components: { default: {} },
leaveGuards: [],
instances: {},
meta: { foo: true },
name: 'name',
path: '/home',
Expand All @@ -55,6 +57,7 @@ describe('normalizeRouteRecord', () => {
aliasOf: undefined,
components: {},
leaveGuards: [],
instances: {},
meta: { foo: true },
name: 'name',
path: '/redirect',
Expand All @@ -77,6 +80,7 @@ describe('normalizeRouteRecord', () => {
children: [{ path: '/child' }],
components: { one: {} },
leaveGuards: [],
instances: {},
meta: { foo: true },
name: 'name',
path: '/home',
Expand All @@ -95,6 +99,7 @@ describe('normalizeRouteRecord', () => {
aliasOf: undefined,
components: {},
leaveGuards: [],
instances: {},
meta: {},
name: undefined,
path: '/redirect',
Expand Down
2 changes: 2 additions & 0 deletions __tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface RouteRecordViewLoose
'path' | 'name' | 'components' | 'children' | 'meta' | 'beforeEnter'
> {
leaveGuards?: any
instances: Record<string, any>
props?: RouteRecordCommon['props']
aliasOf: RouteRecordViewLoose | undefined
}
Expand All @@ -53,6 +54,7 @@ export interface MatcherLocationNormalizedLoose {
redirectedFrom?: Partial<MatcherLocationNormalized>
meta: any
matched: Partial<RouteRecordViewLoose>[]
instances: Record<string, any>
}

declare global {
Expand Down
2 changes: 1 addition & 1 deletion playground/views/ComponentWithData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getData, delay } from '../api'
export default defineComponent({
name: 'ComponentWithData',
async setup() {
const data = reactive({ other: null })
const data = reactive({ other: 'old' })
data.fromApi = await getData()
// TODO: add sample with onBeforeRouteUpdate()
Expand Down
33 changes: 25 additions & 8 deletions src/components/View.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import {
PropType,
computed,
InjectionKey,
Ref,
ref,
ComponentPublicInstance,
ComputedRef,
} from 'vue'
import { RouteRecordNormalized } from '../matcher/types'
import { routeKey } from '../injectKeys'
import { RouteComponent } from '../types'
import { RouteLocationMatched } from '../types'

// TODO: make it work with no symbols too for IE
export const matchedRouteKey = Symbol() as InjectionKey<
Ref<RouteRecordNormalized>
ComputedRef<RouteLocationMatched | undefined>
>

export const View = defineComponent({
Expand All @@ -31,13 +32,16 @@ export const View = defineComponent({
const depth: number = inject('routerViewDepth', 0)
provide('routerViewDepth', depth + 1)

const matchedRoute = computed(() => route.value.matched[depth])
const ViewComponent = computed<RouteComponent | undefined>(
const matchedRoute = computed(
() => route.value.matched[depth] as RouteLocationMatched | undefined
)
const ViewComponent = computed(
() => matchedRoute.value && matchedRoute.value.components[props.name]
)

const propsData = computed(() => {
const { props } = matchedRoute.value
// propsData only gets called if ViewComponent.value exists and it depends on matchedRoute.value
const { props } = matchedRoute.value!
if (!props) return {}
if (props === true) return route.value.params

Expand All @@ -46,9 +50,22 @@ export const View = defineComponent({

provide(matchedRouteKey, matchedRoute)

const viewRef = ref<ComponentPublicInstance>()

function onVnodeMounted() {
// if we mount, there is a matched record
matchedRoute.value!.instances[props.name] = viewRef.value
// TODO: trigger beforeRouteEnter hooks
}

return () => {
return ViewComponent.value
? h(ViewComponent.value as any, { ...propsData.value, ...attrs })
? h(ViewComponent.value as any, {
...propsData.value,
...attrs,
onVnodeMounted,
ref: viewRef,
})
: null
}
},
Expand Down
1 change: 1 addition & 0 deletions src/matcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export function normalizeRouteRecord(
props: record.props || false,
meta: record.meta || {},
leaveGuards: [],
instances: {},
aliasOf: undefined,
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/matcher/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface RouteRecordNormalized {
meta: Exclude<RouteRecordMultipleViews['meta'], void>
props: Exclude<RouteRecordCommon['props'], void>
beforeEnter: RouteRecordMultipleViews['beforeEnter']
leaveGuards: NavigationGuard[]
leaveGuards: NavigationGuard<undefined>[]
// TODO: should be ComponentPublicInstance but breaks Immutable type
instances: Record<string, {} | undefined | null>
aliasOf: RouteRecordNormalized | undefined
}
7 changes: 5 additions & 2 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface Router {
push(to: RouteLocation): Promise<RouteLocationNormalizedResolved>
replace(to: RouteLocation): Promise<RouteLocationNormalizedResolved>

beforeEach(guard: NavigationGuard): ListenerRemover
beforeEach(guard: NavigationGuard<undefined>): ListenerRemover
afterEach(guard: PostNavigationGuard): ListenerRemover

onError(handler: ErrorHandler): ListenerRemover
Expand All @@ -94,7 +94,7 @@ export function createRouter({
}: RouterOptions): Router {
const matcher = createRouterMatcher(routes, {})

const beforeGuards = useCallbacks<NavigationGuard>()
const beforeGuards = useCallbacks<NavigationGuard<undefined>>()
const afterGuards = useCallbacks<PostNavigationGuard>()
const currentRoute = ref<RouteLocationNormalizedResolved>(
START_LOCATION_NORMALIZED
Expand Down Expand Up @@ -275,6 +275,9 @@ export function createRouter({
for (const guard of record.leaveGuards) {
guards.push(guardToPromiseFn(guard, to, from))
}

// free the references
record.instances = {}
}

// run the queue of per route beforeRouteLeave guards
Expand Down
13 changes: 7 additions & 6 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LocationQuery, LocationQueryRaw } from '../utils/query'
import { PathParserOptions } from '../matcher/path-parser-ranker'
import { markNonReactive, ComponentOptions } from 'vue'
import { markNonReactive, ComponentOptions, ComponentPublicInstance } from 'vue'
import { RouteRecordNormalized } from '../matcher/types'

export type Lazy<T> = () => Promise<T>
Expand Down Expand Up @@ -102,16 +102,17 @@ export interface RouteLocationNormalized {
// }

// TODO: type this for beforeRouteUpdate and beforeRouteLeave
// TODO: support arrays
export interface RouteComponentInterface {
beforeRouteEnter?: NavigationGuard<void>
beforeRouteEnter?: NavigationGuard<undefined>
/**
* Guard called when the router is navigating away from the current route
* that is rendering this component.
* @param to RouteLocation we are navigating to
* @param from RouteLocation we are navigating from
* @param next function to validate, cancel or modify (by redirectering) the navigation
*/
beforeRouteLeave?: NavigationGuard<void>
beforeRouteLeave?: NavigationGuard
/**
* Guard called whenever the route that renders this component has changed but
* it is reused for the new route. This allows you to guard for changes in params,
Expand All @@ -120,7 +121,7 @@ export interface RouteComponentInterface {
* @param from RouteLocation we are navigating from
* @param next function to validate, cancel or modify (by redirectering) the navigation
*/
beforeRouteUpdate?: NavigationGuard<void>
beforeRouteUpdate?: NavigationGuard
}

// TODO: allow defineComponent export type RouteComponent = (Component | ReturnType<typeof defineComponent>) &
Expand All @@ -137,7 +138,7 @@ export interface RouteRecordCommon {
| Record<string, any>
| ((to: RouteLocationNormalized) => Record<string, any>)
// TODO: beforeEnter has no effect with redirect, move and test
beforeEnter?: NavigationGuard | NavigationGuard[]
beforeEnter?: NavigationGuard<undefined> | NavigationGuard<undefined>[]
meta?: Record<string | number | symbol, any>
// TODO: only allow a subset?
// TODO: RFC: remove this and only allow global options
Expand Down Expand Up @@ -223,7 +224,7 @@ export interface NavigationGuardCallback {

export type NavigationGuardNextCallback = (vm: any) => any

export interface NavigationGuard<V = void> {
export interface NavigationGuard<V = ComponentPublicInstance> {
(
this: V,
// TODO: we could maybe add extra information like replace: true/false
Expand Down
17 changes: 13 additions & 4 deletions src/utils/guardToPromiseFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ import {
NavigationError,
NavigationRedirectError,
} from '../errors'
import { ComponentPublicInstance } from 'vue'

export function guardToPromiseFn(
guard: NavigationGuard,
guard: NavigationGuard<undefined>,
to: RouteLocationNormalized,
from: RouteLocationNormalizedResolved
// record?: RouteRecordNormalized
from: RouteLocationNormalizedResolved,
instance?: undefined
): () => Promise<void>
export function guardToPromiseFn<
ThisType extends ComponentPublicInstance | undefined
>(
guard: NavigationGuard<ThisType>,
to: RouteLocationNormalized,
from: RouteLocationNormalizedResolved,
instance: ThisType
): () => Promise<void> {
return () =>
new Promise((resolve, reject) => {
Expand Down Expand Up @@ -52,6 +61,6 @@ export function guardToPromiseFn(
}
}

guard(to, from, next)
guard.call(instance, to, from, next)
})
}
Loading

0 comments on commit 7053413

Please sign in to comment.