]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: loadRouteLocation()
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 2 May 2022 16:18:59 +0000 (18:18 +0200)
committerEduardo San Martin Morote <posva@users.noreply.github.com>
Thu, 30 Jun 2022 07:59:00 +0000 (09:59 +0200)
Close #1048

Allows loading a route location to be passed to `<RouterView>`

__tests__/guards/loadRouteLocation.spec.ts [new file with mode: 0644]
src/index.ts
src/navigationGuards.ts

diff --git a/__tests__/guards/loadRouteLocation.spec.ts b/__tests__/guards/loadRouteLocation.spec.ts
new file mode 100644 (file)
index 0000000..17ef885
--- /dev/null
@@ -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'
+    )
+  })
+})
index adfd8207e003a0de84c091e99739f0202bda99d5..d3d2851df8d5f0026493ab5fc7f5368d7e7c9ff7 100644 (file)
@@ -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'
index b0b7fdff03c06a529cdb84232df94c00de593038..8a6091c96ff6798074cb56002f04a933c2d1735c 100644 (file)
@@ -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 `<RouterView>`.
+ *
+ * @param route - resolved route to load
+ */
+export function loadRouteLocation(
+  route: RouteLocationNormalized
+): Promise<RouteLocationNormalizedLoaded> {
+  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<RouteComponent>)().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<Promise<RouteComponent | null | undefined>>)
+            )
+        )
+      ).then(() => route as RouteLocationNormalizedLoaded)
+}