Skip to content

Commit

Permalink
feat: inject in nested loaders
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Dec 19, 2023
1 parent 9d95e27 commit b0aa0b3
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 8 deletions.
87 changes: 86 additions & 1 deletion src/data-fetching_new/defineLoader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @vitest-environment happy-dom
*/
import { App, Ref, defineComponent, shallowRef } from 'vue'
import { App, Ref, defineComponent, inject, shallowRef } from 'vue'
import { defineLoader } from './defineLoader'
import { expectType } from 'ts-expect'
import {
Expand Down Expand Up @@ -154,6 +154,91 @@ describe('defineLoader', () => {
expect(spy).toHaveBeenCalledTimes(3)
expect(data.value).toEqual('resolved 3')
})

describe('app.runWithContext()', () => {
it('can inject globals', async () => {
const { wrapper, router, useData, app } = singleLoaderOneRoute(
defineLoader(async () => {
return inject('key', 'ko')
})
)
app.provide('key', 'ok')
await router.push('/fetch')
const { data } = useData()
expect(data.value).toEqual('ok')
})

it('can inject globals in nested loaders', async () => {
const nestedLoader = defineLoader(async () => {
return inject('key', 'ko')
})
const { wrapper, router, useData, app } = singleLoaderOneRoute(
defineLoader(async () => {
return await nestedLoader()
})
)
app.provide('key', 'ok')
await router.push('/fetch')
const { data } = useData()
expect(data.value).toEqual('ok')
})

it('can inject globals in nested loaders that run after other loaders', async () => {
const l1 = defineLoader(async () => {
return inject('key', 'ko')
})
const l2 = defineLoader(async () => {
return inject('key', 'ko')
})
const { wrapper, router, useData, app } = singleLoaderOneRoute(
defineLoader(async () => {
const a = await l1()
const b = await l2()
return `${a},${b}`
})
)
app.provide('key', 'ok')
await router.push('/fetch')
const { data } = useData()
expect(data.value).toEqual('ok,ok')
})

it('can inject globals when refreshed', async () => {
const { wrapper, router, useData, app } = singleLoaderOneRoute(
defineLoader(async () => {
return inject('key', 'ko')
})
)
await router.push('/fetch')
const { data, refresh } = useData()
expect(data.value).not.toBe('ok')
app.provide('key', 'ok')
await refresh()
expect(data.value).toBe('ok')
})

it('can inject globals in nested loaders when refreshed', async () => {
const l1 = defineLoader(async () => {
return inject('key', 'ko')
})
const l2 = defineLoader(async () => {
return inject('key', 'ko')
})
const { wrapper, router, useData, app } = singleLoaderOneRoute(
defineLoader(async () => {
const a = await l1()
const b = await l2()
return `${a},${b}`
})
)
await router.push('/fetch')
const { data, refresh } = useData()
expect(data.value).not.toBe('ok,ok')
app.provide('key', 'ok')
await refresh()
expect(data.value).toBe('ok,ok')
})
})
})

it(`should abort the navigation if a non lazy loader throws, commit: ${commit}`, async () => {
Expand Down
8 changes: 6 additions & 2 deletions src/data-fetching_new/defineLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
_DataMaybeLazy,
} from './createDataLoader'
import {
APP_KEY,
IS_USE_DATA_LOADER_KEY,
LOADER_ENTRIES_KEY,
STAGED_NO_VALUE,
Expand Down Expand Up @@ -223,7 +224,7 @@ export function defineLoader<
// console.log(
// `🔁 loading from useData for "${options.key}": "${route.fullPath}"`
// )
load(route, router, parentEntry)
router[APP_KEY].runWithContext(() => load(route, router, parentEntry))
}

entry = entries.get(loader)!
Expand All @@ -245,7 +246,10 @@ export function defineLoader<
pending,
refresh: (
to: RouteLocationNormalizedLoaded = router.currentRoute.value
) => load(to, router).then(() => entry!.commit(to)),
) =>
router[APP_KEY].runWithContext(() => load(to, router)).then(() =>
entry!.commit(to)
),
} satisfies UseDataLoaderResult

// load ensures there is a pending load
Expand Down
10 changes: 5 additions & 5 deletions src/data-fetching_new/meta-extensions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { App } from 'vue'
import type { DataLoaderEntryBase, UseDataLoader } from './createDataLoader'
import type {
DataLoaderEntryBase,
UseDataLoader,
_UseLoaderState,
} from './createDataLoader'
import type {
APP_KEY,
LOADER_ENTRIES_KEY,
LOADER_SET_KEY,
PENDING_LOCATION_KEY,
Expand Down Expand Up @@ -33,6 +31,8 @@ declare module 'vue-router' {
* @internal
*/
[PENDING_LOCATION_KEY]: RouteLocationNormalizedLoaded | null

[APP_KEY]: App<unknown>
}

interface RouteMeta {
Expand Down
6 changes: 6 additions & 0 deletions src/data-fetching_new/navigation-guard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Router } from 'vue-router'
import { effectScope, type App, type EffectScope } from 'vue'
import {
APP_KEY,
LOADER_ENTRIES_KEY,
LOADER_SET_KEY,
PENDING_LOCATION_KEY,
Expand Down Expand Up @@ -32,6 +33,9 @@ export function setupLoaderGuard(
// Access to the entries map for convenience
router[LOADER_ENTRIES_KEY] = new WeakMap()

// Access to `app.runWithContext()`
router[APP_KEY] = app

// guard to add the loaders to the meta property
const removeLoaderGuard = router.beforeEach((to) => {
// global pending location, used by nested loaders to know if they should load or not
Expand Down Expand Up @@ -112,6 +116,8 @@ export function setupLoaderGuard(
.runWithContext(() => loader._.load(to, router))
.then(() => {
// for immediate loaders, the load function handles this
// NOTE: it would be nice to also have here the immediate commit
// but running it here is too late for nested loaders
if (commit === 'after-load') {
return loader
}
Expand Down
6 changes: 6 additions & 0 deletions src/data-fetching_new/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ export const PENDING_LOCATION_KEY = Symbol()
* @internal
*/
export const STAGED_NO_VALUE = Symbol()

/**
* Gives access to the current app and it's `runWithContext` method.
* @internal
*/
export const APP_KEY = Symbol()

0 comments on commit b0aa0b3

Please sign in to comment.