import { RouterOptions } from '../src/router'
import { RouteComponent } from '../src/types'
import { ticks } from './utils'
+import { FunctionalComponent, h } from 'vue'
function newRouter(options: Partial<RouterOptions> = {}) {
let history = createMemoryHistory()
matched: [],
})
})
+
+ it('works with functional components', async () => {
+ const Functional: FunctionalComponent = () => h('div', 'functional')
+ Functional.displayName = 'Functional'
+
+ const { router } = newRouter({
+ routes: [{ path: '/foo', component: Functional }],
+ })
+
+ await expect(router.push('/foo')).resolves.toBe(undefined)
+ })
})
import { mockWarn } from 'jest-mock-warn'
import { createMemoryHistory, createRouter } from '../src'
-import { defineComponent } from 'vue'
+import { defineComponent, FunctionalComponent, h } from 'vue'
let component = defineComponent({})
router.push('/b')
})
+
+ it('warns if a non valid function is passed as a component', async () => {
+ const Functional: FunctionalComponent = () => h('div', 'functional')
+ // Functional should have a displayName to avoid the warning
+
+ const router = createRouter({
+ history: createMemoryHistory(),
+ routes: [{ path: '/foo', component: Functional }],
+ })
+
+ await expect(router.push('/foo')).resolves.toBe(undefined)
+ expect('with path "/foo" is a function').toHaveBeenWarned()
+ })
})
NavigationGuard,
} from './types'
import { routerKey, routeLocationKey } from './injectionSymbols'
+import { warn } from './warning'
declare module '@vue/runtime-core' {
interface ComponentCustomOptions {
// @ts-ignore: see above
router._started = true
router.push(router.history.location.fullPath).catch(err => {
- if (__DEV__)
- console.error('Unhandled error when starting the router', err)
+ if (__DEV__) warn('Unexpected error when starting the router:', err)
})
}
isRouteLocation,
Lazy,
RouteComponent,
+ RawRouteComponent,
} from './types'
import {
NavigationFailure,
NavigationRedirectError,
} from './errors'
-import { ComponentPublicInstance } from 'vue'
+import { ComponentPublicInstance, ComponentOptions } from 'vue'
import { inject, getCurrentInstance, warn } from 'vue'
import { matchedRouteKey } from './injectionSymbols'
import { RouteRecordNormalized } from './matcher/types'
for (const record of matched) {
for (const name in record.components) {
const rawComponent = record.components[name]
- if (typeof rawComponent === 'function') {
+ if (isRouteComponent(rawComponent)) {
+ // __vccOpts is added by vue-class-component and contain the regular options
+ let options: ComponentOptions =
+ (rawComponent as any).__vccOpts || rawComponent
+ const guard = options[guardType]
+ guard &&
+ guards.push(guardToPromiseFn(guard, to, from, record.instances[name]))
+ } else {
// start requesting the chunk already
- const componentPromise = (rawComponent as Lazy<RouteComponent>)().catch(
- () => null
- )
+ let componentPromise: Promise<RouteComponent | null> = (rawComponent as Lazy<
+ RouteComponent
+ >)()
+
+ if (__DEV__ && !('catch' in componentPromise)) {
+ warn(
+ `Component "${name}" at record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`
+ )
+ componentPromise = Promise.resolve(componentPromise as RouteComponent)
+ } else {
+ componentPromise = componentPromise.catch(() => null)
+ }
+
guards.push(() =>
componentPromise.then(resolved => {
if (!resolved)
// @ts-ignore: the options types are not propagated to Component
const guard: NavigationGuard = resolvedComponent[guardType]
return (
- // @ts-ignore: the guards matched the instance type
guard &&
guardToPromiseFn(guard, to, from, record.instances[name])()
)
})
)
- } else {
- const guard = rawComponent[guardType]
- guard &&
- // @ts-ignore: the guards matched the instance type
- guards.push(guardToPromiseFn(guard, to, from, record.instances[name]))
}
}
}
return guards
}
+
+/**
+ * Allows differentiating lazy components from functional components and vue-class-component
+ * @param component
+ */
+function isRouteComponent(
+ component: RawRouteComponent
+): component is RouteComponent {
+ return (
+ typeof component === 'object' ||
+ 'displayName' in component ||
+ 'props' in component ||
+ '__vccOpts' in component
+ )
+}
import { LocationQuery, LocationQueryRaw } from '../query'
import { PathParserOptions } from '../matcher'
-import { Ref, ComputedRef, ComponentOptions } from 'vue'
+import { Ref, ComputedRef, Component } from 'vue'
import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
import { HistoryState } from '../history/common'
import { NavigationFailure } from '../errors'
matched: RouteRecordNormalized[] // non-enumerable
}
-export type RouteComponent = ComponentOptions
+export type RouteComponent = Component
export type RawRouteComponent = RouteComponent | Lazy<RouteComponent>
export type RouteRecordName = string | symbol