From: Eduardo San Martin Morote Date: Mon, 2 May 2022 16:18:59 +0000 (+0200) Subject: feat: loadRouteLocation() X-Git-Tag: v4.1.0~128 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d352ee4d76f5e7d60aba03d275df9a35459bd300;p=thirdparty%2Fvuejs%2Frouter.git feat: loadRouteLocation() Close #1048 Allows loading a route location to be passed to `` --- diff --git a/__tests__/guards/loadRouteLocation.spec.ts b/__tests__/guards/loadRouteLocation.spec.ts new file mode 100644 index 00000000..17ef8856 --- /dev/null +++ b/__tests__/guards/loadRouteLocation.spec.ts @@ -0,0 +1,186 @@ +import { isRouteComponent, loadRouteLocation } from '../../src/navigationGuards' +import { + START_LOCATION_NORMALIZED, + RouteRecordRaw, + RouteLocationRaw, +} from '../../src/types' +import { components } from '../utils' +import { normalizeRouteRecord } from '../../src/matcher' +import { RouteRecordNormalized } from '../../src/matcher/types' +import { createMemoryHistory, createRouter } from '../../src' +import { FunctionalComponent } from 'vue' + +const FunctionalHome: FunctionalComponent = () => null +FunctionalHome.displayName = 'Home' + +describe('loadRouteLocation', () => { + async function testLoadRoute( + routes: RouteRecordRaw[], + to: RouteLocationRaw = '/' + ) { + const router = createRouter({ + history: createMemoryHistory(), + routes, + }) + + const loaded = await loadRouteLocation(router.resolve(to)) + for (const record of loaded.matched) { + for (const name in record.components) { + const comp = record.components[name] + expect(isRouteComponent(comp)).toBe(true) + } + } + } + + it('fallthrough non promises', async () => { + expect.assertions(3) + await testLoadRoute([{ path: '/', component: components.Home }]) + await testLoadRoute([{ path: '/', component: FunctionalHome }]) + await testLoadRoute([ + { + path: '/', + components: { name: FunctionalHome }, + }, + ]) + }) + + it('resolves simple promises', async () => { + expect.assertions(3) + await testLoadRoute([ + { path: '/', component: () => Promise.resolve(components.Home) }, + ]) + await testLoadRoute([ + { path: '/', component: () => Promise.resolve(FunctionalHome) }, + ]) + await testLoadRoute([ + { + path: '/', + components: { name: () => Promise.resolve(FunctionalHome) }, + }, + ]) + }) + + it('works with nested routes', async () => { + expect.assertions(4) + await testLoadRoute([ + { + path: '/', + component: () => Promise.resolve(components.Home), + children: [ + { + path: '', + component: () => Promise.resolve(components.Home), + children: [ + { + path: '', + component: () => Promise.resolve(components.Home), + children: [ + { + path: '', + components: { + name: () => Promise.resolve(components.Home), + }, + }, + ], + }, + ], + }, + ], + }, + ]) + }) + + it('throws with non loadable routes', async () => { + expect.assertions(1) + await expect( + testLoadRoute([{ path: '/', redirect: '/foo' }]) + ).rejects.toBeInstanceOf(Error) + }) + + it('works with nested routes with redirect', async () => { + expect.assertions(2) + testLoadRoute( + [ + { + path: '/', + redirect: '/foo', + children: [ + { path: 'foo', component: () => Promise.resolve(components.Home) }, + ], + }, + ], + '/foo' + ) + testLoadRoute( + [ + { + path: '/', + children: [ + { path: '', redirect: '/foo' }, + { path: 'foo', component: () => Promise.resolve(components.Home) }, + ], + }, + ], + '/foo' + ) + }) + + it('works with aliases through alias', async () => { + expect.assertions(3) + await testLoadRoute([ + { + path: '/a', + alias: '/', + component: () => Promise.resolve(components.Home), + }, + ]) + await testLoadRoute([ + { + path: '/a', + alias: '/', + component: () => Promise.resolve(FunctionalHome), + }, + ]) + await testLoadRoute([ + { + path: '/a', + alias: '/', + components: { name: () => Promise.resolve(FunctionalHome) }, + }, + ]) + }) + + it('works with aliases through original', async () => { + expect.assertions(3) + await testLoadRoute( + [ + { + path: '/a', + alias: '/', + component: () => Promise.resolve(components.Home), + }, + ], + '/a' + ) + await testLoadRoute( + [ + { + path: '/a', + alias: '/', + component: () => Promise.resolve(FunctionalHome), + }, + ], + '/a' + ) + await testLoadRoute( + [ + { + path: '/a', + alias: '/', + components: { name: () => Promise.resolve(FunctionalHome) }, + }, + ], + '/a' + ) + }) +}) diff --git a/src/index.ts b/src/index.ts index adfd8207..d3d2851d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,7 +66,11 @@ export type { Router, RouterOptions, RouterScrollBehavior } from './router' export { NavigationFailureType, isNavigationFailure } from './errors' export type { NavigationFailure } from './errors' -export { onBeforeRouteLeave, onBeforeRouteUpdate } from './navigationGuards' +export { + onBeforeRouteLeave, + onBeforeRouteUpdate, + loadRouteLocation, +} from './navigationGuards' export { RouterLink, useLink } from './RouterLink' export type { RouterLinkProps, UseLinkOptions } from './RouterLink' export { RouterView } from './RouterView' diff --git a/src/navigationGuards.ts b/src/navigationGuards.ts index b0b7fdff..8a6091c9 100644 --- a/src/navigationGuards.ts +++ b/src/navigationGuards.ts @@ -335,10 +335,11 @@ export function extractComponentsGuards( /** * Allows differentiating lazy components from functional components and vue-class-component + * @internal * * @param component */ -function isRouteComponent( +export function isRouteComponent( component: RawRouteComponent ): component is RouteComponent { return ( @@ -348,3 +349,48 @@ function isRouteComponent( '__vccOpts' in component ) } + +/** + * Ensures a route is loaded so it can be passed as o prop to ``. + * + * @param route - resolved route to load + */ +export function loadRouteLocation( + route: RouteLocationNormalized +): Promise { + return route.matched.every(record => record.redirect) + ? Promise.reject(new Error('Cannot load a route that redirects.')) + : Promise.all( + route.matched.map( + record => + record.components && + Promise.all( + Object.keys(record.components).reduce((promises, name) => { + const rawComponent = record.components![name] + if ( + typeof rawComponent === 'function' && + !('displayName' in rawComponent) + ) { + promises.push( + (rawComponent as Lazy)().then(resolved => { + if (!resolved) + return Promise.reject( + new Error( + `Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.` + ) + ) + const resolvedComponent = isESModule(resolved) + ? resolved.default + : resolved + // replace the function with the resolved component + // cannot be null or undefined because we went into the for loop + record.components![name] = resolvedComponent + }) + ) + } + return promises + }, [] as Array>) + ) + ) + ).then(() => route as RouteLocationNormalizedLoaded) +}