diff --git a/packages/playground/env.d.ts b/packages/playground/env.d.ts index befb0fbf1..1b310ac1b 100644 --- a/packages/playground/env.d.ts +++ b/packages/playground/env.d.ts @@ -1,8 +1,40 @@ /// /// -declare module '*.vue' { - import { Component } from 'vue' - var component: Component - export default component +// declare module '*.vue' { +// import { Component } from 'vue' +// var component: Component +// export default component +// } + +declare module '@vue-router' { + import type { Ref } from 'vue' + + export interface LoaderResult { + data: Load extends _Loader ? R : unknown + isLoading: Ref + } + + export function useLoader( + name?: Name + ): RouteNamedMap[Name] + + export interface _Loader { + (to: { params: Params }): Promise + } + + export function defineLoader( + name: Name, + loader: _Loader, Result> + ): _Loader, Result> + + export type RouteNames = 'user' | 'admin' | 'home' | 'about' + + export interface RouteNamedMap { + user: LoaderResult + } + + export type RouteParams = { + user: { id: string } + }[Name] } diff --git a/packages/playground/src/api/index.ts b/packages/playground/src/api/index.ts index 628521c93..825139bae 100644 --- a/packages/playground/src/api/index.ts +++ b/packages/playground/src/api/index.ts @@ -9,3 +9,11 @@ export async function getData() { time: Date.now(), } } + +export async function getUserById(id: string) { + await delay(200) + return { + id: 1, + name: 'Eduardo', + } +} diff --git a/packages/playground/src/router.ts b/packages/playground/src/router.ts index 4270b6a1c..80e4e32d3 100644 --- a/packages/playground/src/router.ts +++ b/packages/playground/src/router.ts @@ -1,5 +1,10 @@ -import { createRouter, createWebHistory, RouterView } from 'vue-router' -import type { RouterLinkTyped } from 'vue-router' +import { + createRouter, + createWebHistory, + RouterView, + type RouteLocationNormalized, + type RouteRecordRaw, +} from 'vue-router' import Home from './views/Home.vue' import Nested from './views/Nested.vue' import NestedWithId from './views/NestedWithId.vue' @@ -23,150 +28,186 @@ let removeRoute: (() => void) | undefined const TransparentWrapper: FunctionalComponent = () => h(RouterView) TransparentWrapper.displayName = 'NestedView' -export const routerHistory = createWebHistory() -export const router = createRouter({ - history: routerHistory, - strict: true, - routes: [ - { path: '/home', redirect: '/' }, - { - path: '/', - components: { default: Home, other: component }, - props: { default: to => ({ waited: to.meta.waitedFor }) }, +const routes: RouteRecordRaw[] = [ + { path: '/home', redirect: '/' }, + { + path: '/', + components: { default: Home, other: component }, + props: { default: to => ({ waited: to.meta.waitedFor }) }, + }, + { + path: '/always-redirect', + redirect: () => ({ + name: 'user', + params: { id: String(Math.round(Math.random() * 100)) }, + }), + }, + { path: '/users/:id', name: 'user', component: User, props: true }, + { path: '/documents/:id', name: 'docs', component: User, props: true }, + { path: '/optional/:id?', name: 'optional', component: User, props: true }, + { path: encodeURI('/n/€'), name: 'euro', component }, + { path: '/n/:n', name: 'increment', component }, + { path: '/multiple/:a/:b', name: 'multiple', component }, + { path: '/long-:n', name: 'long', component: LongView }, + { + path: '/lazy', + meta: { transition: 'slide-left' }, + component: async () => { + await delay(500) + return component }, - { - path: '/always-redirect', - redirect: () => ({ - name: 'user', - params: { id: String(Math.round(Math.random() * 100)) }, - }), + }, + { + path: '/with-guard/:n', + name: 'guarded', + component, + beforeEnter(to) { + if (to.params.n !== 'valid') return false }, - { path: '/users/:id', name: 'user', component: User, props: true }, - { path: '/documents/:id', name: 'docs', component: User, props: true }, - { path: '/optional/:id?', name: 'optional', component: User, props: true }, - { path: encodeURI('/n/€'), name: 'euro', component }, - { path: '/n/:n', name: 'increment', component }, - { path: '/multiple/:a/:b', name: 'multiple', component }, - { path: '/long-:n', name: 'long', component: LongView }, - { - path: '/lazy', - meta: { transition: 'slide-left' }, - component: async () => { - await delay(500) - return component + }, + { path: '/cant-leave', component: GuardedWithLeave }, + { + path: '/children', + name: 'WithChildren', + component: Nested, + children: [ + { path: '', alias: 'alias', name: 'default-child', component: Nested }, + { path: 'a', name: 'a-child', component: Nested }, + { + path: 'b', + name: 'WithChildrenB', + component: Nested, + children: [ + { + path: '', + name: 'b-child', + component: Nested, + }, + { path: 'a2', component: Nested }, + { path: 'b2', component: Nested }, + ], }, - }, - { - path: '/with-guard/:n', - name: 'guarded', - component, - beforeEnter(to) { - if (to.params.n !== 'valid') return false + ], + }, + { path: '/with-data', component: ComponentWithData, name: 'WithData' }, + { path: '/rep/:a*', component: RepeatedParams, name: 'repeat' }, + { path: '/:data(.*)', component: NotFound, name: 'NotFound' }, + { + path: '/nested', + alias: '/anidado', + component: Nested, + name: 'Nested', + children: [ + { + path: 'nested', + alias: 'a', + name: 'NestedNested', + component: Nested, + children: [ + { + name: 'NestedNestedNested', + path: 'nested', + component: Nested, + }, + ], }, - }, - { path: '/cant-leave', component: GuardedWithLeave }, - { - path: '/children', - name: 'WithChildren', - component: Nested, - children: [ - { path: '', alias: 'alias', name: 'default-child', component: Nested }, - { path: 'a', name: 'a-child', component: Nested }, - { - path: 'b', - name: 'WithChildrenB', - component: Nested, - children: [ - { - path: '', - name: 'b-child', - component: Nested, - }, - { path: 'a2', component: Nested }, - { path: 'b2', component: Nested }, - ], - }, - ], - }, - { path: '/with-data', component: ComponentWithData, name: 'WithData' }, - { path: '/rep/:a*', component: RepeatedParams, name: 'repeat' }, - { path: '/:data(.*)', component: NotFound, name: 'NotFound' }, - { - path: '/nested', - alias: '/anidado', - component: Nested, - name: 'Nested', - children: [ - { - path: 'nested', - alias: 'a', - name: 'NestedNested', - component: Nested, - children: [ - { - name: 'NestedNestedNested', - path: 'nested', - component: Nested, - }, - ], - }, - { - path: 'other', - alias: 'otherAlias', - component: Nested, - name: 'NestedOther', - }, - { - path: 'also-as-absolute', - alias: '/absolute', - name: 'absolute-child', - component: Nested, - }, - ], - }, - - { - path: '/parent/:id', - name: 'parent', - component: NestedWithId, - props: true, - alias: '/p/:id', - children: [ - // empty child - { path: '', name: 'child-id', component }, - // child with absolute path. we need to add an `id` because the parent needs it - { path: '/p_:id/absolute-a', alias: 'as-absolute-a', component }, - // same as above but the alias is absolute - { path: 'as-absolute-b', alias: '/p_:id/absolute-b', component }, - ], - }, - { - path: '/dynamic', - name: 'dynamic', - component: Nested, - end: false, - strict: true, - beforeEnter(to) { - if (!removeRoute) { - removeRoute = router.addRoute('dynamic', { - path: 'child', - component: Dynamic, - }) - return to.fullPath - } + { + path: 'other', + alias: 'otherAlias', + component: Nested, + name: 'NestedOther', }, - }, + { + path: 'also-as-absolute', + alias: '/absolute', + name: 'absolute-child', + component: Nested, + }, + ], + }, - { - path: '/admin', - component: TransparentWrapper, - children: [ - { path: '', component }, - { path: 'dashboard', component }, - { path: 'settings', component }, - ], + { + path: '/parent/:id', + name: 'parent', + component: NestedWithId, + props: true, + alias: '/p/:id', + children: [ + // empty child + { path: '', name: 'child-id', component }, + // child with absolute path. we need to add an `id` because the parent needs it + { path: '/p_:id/absolute-a', alias: 'as-absolute-a', component }, + // same as above but the alias is absolute + { path: 'as-absolute-b', alias: '/p_:id/absolute-b', component }, + ], + }, + { + path: '/dynamic', + name: 'dynamic', + component: Nested, + end: false, + strict: true, + beforeEnter(to) { + if (!removeRoute) { + removeRoute = router.addRoute('dynamic', { + path: 'child', + component: Dynamic, + }) + return to.fullPath + } }, - ] as const, + }, + + { + path: '/admin', + component: TransparentWrapper, + children: [ + { path: '', component }, + { path: 'dashboard', component }, + { path: 'settings', component }, + ], + }, +] + +function mergeRouteProps( + record: Exclude, + to: RouteLocationNormalized +) { + const originalProps = record.props + // TODO: named views can have an object + const originalPropsResult = + typeof originalProps === 'function' + ? originalProps(to) + : typeof originalProps === 'boolean' + ? {} + : originalProps + return { ...originalPropsResult, ...to.meta.data } +} + +function setPropsToData(record: RouteRecordRaw) { + if (!('redirect' in record)) { + const originalProps = record.props + record.props = mergeRouteProps.bind(null, record) + } + if (record.children) { + record.children.forEach(setPropsToData) + } +} +routes.forEach(setPropsToData) + +declare module 'vue-router' { + export interface RouteMeta { + pendingRoute?: RouteLocationNormalizedLoaded + load?: () => Promise> | Record + data?: Record + } +} + +export const routerHistory = createWebHistory() +export const router = createRouter({ + history: routerHistory, + strict: true, + routes, async scrollBehavior(to, from, savedPosition) { await scrollWaiter.wait() if (savedPosition) { @@ -181,13 +222,28 @@ export const router = createRouter({ }, }) -declare module 'vue-router' { - export interface Config { - Router: typeof router +router.beforeEach((to, from) => { + delete from.meta.pendingRoute +}) +router.beforeResolve(async (to, from) => { + if (to.meta.load) { + from.meta.pendingRoute = to + to.meta.data = await to.meta.load() + } else { } -} -// router.push({ name: 'user', params: {} }) + from.meta.pendingRoute = to + const loaders = to.matched + .map(record => + // TODO: avoid refetching if the route is already loaded + // Find a strategy to do it + record.meta.load?.() + ) + .filter(Boolean) + + const loadedData = await Promise.all(loaders) + loadedData.forEach((data, i) => {}) +}) const delay = (t: number) => new Promise(resolve => setTimeout(resolve, t)) diff --git a/packages/playground/src/views/UserDetail.vue b/packages/playground/src/views/UserDetail.vue new file mode 100644 index 000000000..d1d778f4b --- /dev/null +++ b/packages/playground/src/views/UserDetail.vue @@ -0,0 +1,39 @@ + + + + + + + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy