unplugin-vue-router
Version:
File based typed routing for Vue Router
205 lines (203 loc) • 6.71 kB
JavaScript
import {
toLazyValue
} from "./chunk-4MKZ4JDN.js";
// src/data-loaders/defineLoader.ts
import {
useRoute,
useRouter
} from "vue-router";
import {
ABORT_CONTROLLER_KEY,
APP_KEY,
IS_USE_DATA_LOADER_KEY,
LOADER_ENTRIES_KEY,
NAVIGATION_RESULTS_KEY,
PENDING_LOCATION_KEY,
STAGED_NO_VALUE,
NavigationResult,
getCurrentContext,
setCurrentContext,
IS_SSR_KEY
} from "unplugin-vue-router/data-loaders";
import { shallowRef } from "vue";
function defineBasicLoader(nameOrLoader, _loaderOrOptions, opts) {
const loader = typeof nameOrLoader === "function" ? nameOrLoader : _loaderOrOptions;
opts = typeof _loaderOrOptions === "object" ? _loaderOrOptions : opts;
const options = {
...DEFAULT_DEFINE_LOADER_OPTIONS,
...opts,
// avoid opts overriding with `undefined`
commit: opts?.commit || DEFAULT_DEFINE_LOADER_OPTIONS.commit
};
function load(to, router, from, parent) {
const entries = router[LOADER_ENTRIES_KEY];
const isSSR = router[IS_SSR_KEY];
if (!entries.has(loader)) {
entries.set(loader, {
// force the type to match
data: shallowRef(),
isLoading: shallowRef(false),
error: shallowRef(),
to,
options,
children: /* @__PURE__ */ new Set(),
resetPending() {
this.pendingLoad = null;
this.pendingTo = null;
},
pendingLoad: null,
pendingTo: null,
staged: STAGED_NO_VALUE,
stagedError: null,
commit
});
}
const entry = entries.get(loader);
if (entry.pendingTo === to && entry.pendingLoad) {
return entry.pendingLoad;
}
const { error, isLoading, data } = entry;
const initialRootData = router[INITIAL_DATA_KEY];
const key = options.key || "";
let initialData = STAGED_NO_VALUE;
if (initialRootData && key in initialRootData) {
initialData = initialRootData[key];
delete initialRootData[key];
}
if (initialData !== STAGED_NO_VALUE) {
data.value = initialData;
return entry.pendingLoad = Promise.resolve();
}
entry.pendingTo = to;
isLoading.value = true;
const currentContext = getCurrentContext();
if (process.env.NODE_ENV === "development") {
if (parent !== currentContext[0]) {
console.warn(
`\u274C\u{1F476} "${options.key}" has a different parent than the current context. This shouldn't be happening. Please report a bug with a reproduction to https://github.com/posva/unplugin-vue-router/`
);
}
}
setCurrentContext([entry, router, to]);
entry.staged = STAGED_NO_VALUE;
entry.stagedError = error.value;
const currentLoad = Promise.resolve(
loader(to, { signal: to.meta[ABORT_CONTROLLER_KEY]?.signal })
).then((d) => {
if (entry.pendingLoad === currentLoad) {
if (d instanceof NavigationResult) {
to.meta[NAVIGATION_RESULTS_KEY].push(d);
} else {
entry.staged = d;
entry.stagedError = null;
}
}
}).catch((e) => {
if (entry.pendingLoad === currentLoad) {
entry.stagedError = e;
if (!toLazyValue(options.lazy, to, from) || isSSR) {
throw e;
}
}
}).finally(() => {
setCurrentContext(currentContext);
if (entry.pendingLoad === currentLoad) {
isLoading.value = false;
if (options.commit === "immediate" || // outside of navigation
!router[PENDING_LOCATION_KEY]) {
entry.commit(to);
}
} else {
}
});
setCurrentContext(currentContext);
entry.pendingLoad = currentLoad;
return currentLoad;
}
function commit(to) {
if (this.pendingTo === to) {
if (process.env.NODE_ENV === "development") {
if (this.staged === STAGED_NO_VALUE) {
console.warn(
`Loader "${options.key}"'s "commit()" was called but there is no staged data.`
);
}
}
if (this.staged !== STAGED_NO_VALUE) {
this.data.value = this.staged;
}
this.error.value = this.stagedError;
this.staged = STAGED_NO_VALUE;
this.stagedError = this.error.value;
this.pendingTo = null;
this.to = to;
for (const childEntry of this.children) {
childEntry.commit(to);
}
}
}
const useDataLoader = () => {
const currentContext = getCurrentContext();
const [parentEntry, _router, _route] = currentContext;
const router = _router || useRouter();
const route = _route || useRoute();
const entries = router[LOADER_ENTRIES_KEY];
let entry = entries.get(loader);
if (
// if the entry doesn't exist, create it with load and ensure it's loading
!entry || // the existing pending location isn't good, we need to load again
parentEntry && entry.pendingTo !== route || // we could also check for: but that would break nested loaders since they need to be always called to be associated with the parent
// && entry.to !== route
// the user managed to render the router view after a valid navigation + a failed navigation
// https://github.com/posva/unplugin-vue-router/issues/495
!entry.pendingLoad
) {
router[APP_KEY].runWithContext(
() => load(route, router, void 0, parentEntry)
);
}
entry = entries.get(loader);
if (parentEntry) {
if (parentEntry === entry) {
console.warn(
`\u{1F476}\u274C "${options.key}" has itself as parent. This shouldn't be happening. Please report a bug with a reproduction to https://github.com/posva/unplugin-vue-router/`
);
}
parentEntry.children.add(entry);
}
const { data, error, isLoading } = entry;
const useDataLoaderResult = {
data,
error,
isLoading,
reload: (to = router.currentRoute.value) => router[APP_KEY].runWithContext(() => load(to, router)).then(
() => entry.commit(to)
)
};
const promise = entry.pendingLoad.then(() => {
return entry.staged === STAGED_NO_VALUE ? data.value : entry.staged;
}).catch((e) => parentEntry ? Promise.reject(e) : null);
setCurrentContext(currentContext);
return Object.assign(promise, useDataLoaderResult);
};
useDataLoader[IS_USE_DATA_LOADER_KEY] = true;
useDataLoader._ = {
load,
options,
// @ts-expect-error: return type has the generics
getEntry(router) {
return router[LOADER_ENTRIES_KEY].get(loader);
}
};
return useDataLoader;
}
var DEFAULT_DEFINE_LOADER_OPTIONS = {
lazy: false,
server: true,
commit: "after-load"
};
var SERVER_INITIAL_DATA_KEY = Symbol();
var INITIAL_DATA_KEY = Symbol();
export {
defineBasicLoader
};