one
Version:
One is a new React Framework that makes Vite serve both native and web.
147 lines (146 loc) • 5.23 kB
JavaScript
import { useCallback, useSyncExternalStore } from "react";
import { useParams, usePathname } from "./hooks";
import { preloadedLoaderData, preloadingLoader } from "./router/router";
import { getLoaderPath } from "./utils/cleanUrl";
import { dynamicImport } from "./utils/dynamicImport";
import { weakKey } from "./utils/weakKey";
import { useServerContext } from "./vite/one-server-only";
const loaderState = {}, subscribers = /* @__PURE__ */ new Set();
function updateState(path, updates) {
loaderState[path] = { ...loaderState[path], ...updates }, subscribers.forEach((callback) => {
callback();
});
}
function subscribe(callback) {
return subscribers.add(callback), () => subscribers.delete(callback);
}
function getLoaderState(path, preloadedData2) {
return loaderState[path] || (loaderState[path] = {
data: preloadedData2,
error: void 0,
promise: void 0,
state: "idle",
hasLoadedOnce: !!preloadedData2
}), loaderState[path];
}
async function refetchLoader(pathname2) {
updateState(pathname2, {
state: "loading",
error: null
});
try {
const cacheBust = `${Date.now()}`, loaderJSUrl2 = getLoaderPath(pathname2, !0, cacheBust), result2 = await (await dynamicImport(loaderJSUrl2)).loader();
updateState(pathname2, {
data: result2,
state: "idle",
timestamp: Date.now(),
hasLoadedOnce: !0
});
} catch (err) {
throw updateState(pathname2, {
error: err,
state: "idle"
}), err;
}
}
function useLoaderState(loader) {
const { loaderProps: loaderPropsFromServerContext, loaderData: loaderDataFromServerContext } = useServerContext() || {}, params = useParams(), pathname = usePathname(), currentPath = pathname.replace(/\/index$/, "").replace(/\/$/, "") || "/";
if (typeof window > "u" && loader)
return { data: useAsyncFn(
loader,
loaderPropsFromServerContext || {
path: pathname,
params
}
), refetch: async () => {
}, state: "idle" };
const serverContextPath = loaderPropsFromServerContext?.path, preloadedData = serverContextPath === currentPath ? loaderDataFromServerContext : void 0, loaderStateEntry = useSyncExternalStore(
subscribe,
() => getLoaderState(currentPath, preloadedData),
() => getLoaderState(currentPath, preloadedData)
), refetch = useCallback(() => refetchLoader(currentPath), [currentPath]);
if (!loader)
return {
refetch,
state: loaderStateEntry.state
};
if (!loaderStateEntry.data && !loaderStateEntry.promise && !loaderStateEntry.hasLoadedOnce && loader) {
const resolvedPreloadData = preloadedLoaderData[currentPath];
if (resolvedPreloadData !== void 0)
delete preloadedLoaderData[currentPath], delete preloadingLoader[currentPath], loaderStateEntry.data = resolvedPreloadData, loaderStateEntry.hasLoadedOnce = !0;
else if (preloadingLoader[currentPath]) {
const promise2 = preloadingLoader[currentPath].then((val) => {
delete preloadingLoader[currentPath], delete preloadedLoaderData[currentPath], updateState(currentPath, {
data: val,
hasLoadedOnce: !0,
promise: void 0
});
}).catch((err) => {
console.error("Error running loader()", err), delete preloadingLoader[currentPath], updateState(currentPath, {
error: err,
promise: void 0
});
});
loaderStateEntry.promise = promise2;
} else {
const loadData = async () => {
try {
const loaderJSUrl = getLoaderPath(currentPath, !0), module = await dynamicImport(loaderJSUrl), result = await module.loader();
updateState(currentPath, {
data: result,
hasLoadedOnce: !0,
promise: void 0
});
} catch (err) {
updateState(currentPath, {
error: err,
promise: void 0
});
}
}, promise = loadData();
loaderStateEntry.promise = promise;
}
}
if (loader) {
if (loaderStateEntry.error && !loaderStateEntry.hasLoadedOnce)
throw loaderStateEntry.error;
if (loaderStateEntry.data === void 0 && loaderStateEntry.promise && !loaderStateEntry.hasLoadedOnce)
throw loaderStateEntry.promise;
return {
data: loaderStateEntry.data,
refetch,
state: loaderStateEntry.state
};
} else
return {
refetch,
state: loaderStateEntry.state
};
}
function useLoader(loader2) {
const { data: data2 } = useLoaderState(loader2);
return data2;
}
const results = /* @__PURE__ */ new Map(), started = /* @__PURE__ */ new Map();
function useAsyncFn(val, props) {
const key = (val ? weakKey(val) : "") + JSON.stringify(props);
if (val && !started.get(key)) {
started.set(key, !0);
let next = val(props);
next instanceof Promise && (next = next.then((final) => {
results.set(key, final);
}).catch((err) => {
console.error("Error running loader()", err), results.set(key, void 0);
})), results.set(key, next);
}
const current = results.get(key);
if (current instanceof Promise)
throw current;
return current;
}
export {
refetchLoader,
useLoader,
useLoaderState
};
//# sourceMappingURL=useLoader.js.map