one
Version:
One is a new React Framework that makes Vite serve both native and web.
360 lines (359 loc) • 13.2 kB
JavaScript
import {
StackActions
} from "@react-navigation/native";
import {
Fragment,
startTransition,
useDeferredValue,
useSyncExternalStore
} from "react";
import { Platform } from "react-native-web";
import { resolveHref } from "../link/href";
import { openExternalURL } from "../link/openExternalURL";
import { resolve } from "../link/path";
import { assertIsReady } from "../utils/assertIsReady";
import { getLoaderPath, getPreloadCSSPath, getPreloadPath } from "../utils/cleanUrl";
import { dynamicImport } from "../utils/dynamicImport";
import { shouldLinkExternally } from "../utils/url";
import { getRouteInfo } from "./getRouteInfo";
import { getRoutes } from "./getRoutes";
import { setLastAction } from "./lastAction";
import { getLinking, resetLinking, setupLinking } from "./linkingConfig";
import { sortRoutes } from "./sortRoutes";
import { getQualifiedRouteComponent } from "./useScreens";
import { preloadRouteModules } from "./useViteRoutes";
import { getNavigateAction } from "./utils/getNavigateAction";
let routeNode = null, rootComponent, hasAttemptedToHideSplash = !1, initialState, rootState, nextState, routeInfo, splashScreenAnimationFrame, navigationRef = null, navigationRefSubscription;
const rootStateSubscribers = /* @__PURE__ */ new Set(), loadingStateSubscribers = /* @__PURE__ */ new Set(), storeSubscribers = /* @__PURE__ */ new Set();
function initialize(context, ref, initialLocation) {
if (cleanUpState(), routeNode = getRoutes(context, {
ignoreEntryPoints: !0,
platform: Platform.OS
}), rootComponent = routeNode ? getQualifiedRouteComponent(routeNode) : Fragment, !routeNode && process.env.NODE_ENV === "production")
throw new Error("No routes found");
if (process.env.ONE_DEBUG_ROUTER && routeNode) {
const formatRouteTree = (node, indent = "", isLast = !0) => {
const prefix = indent + (isLast ? "\u2514\u2500 " : "\u251C\u2500 "), childIndent = indent + (isLast ? " " : "\u2502 "), dynamicBadge = node.dynamic ? ` [${node.dynamic.map((d) => d.name).join(", ")}]` : "", typeBadge = node.type !== "layout" ? ` (${node.type})` : "", routeName = node.route || "/";
let line = `${prefix}${routeName}${dynamicBadge}${typeBadge}`;
const visibleChildren = node.children.filter((child) => !child.internal);
for (let i = 0; i < visibleChildren.length; i++) {
const child = visibleChildren[i], childIsLast = i === visibleChildren.length - 1;
line += `
` + formatRouteTree(child, childIndent, childIsLast);
}
return line;
};
console.info(`[one] \u{1F4CD} Route structure:
${formatRouteTree(routeNode)}`);
}
navigationRef = ref, setupLinkingAndRouteInfo(initialLocation), subscribeToNavigationChanges();
}
function cleanUpState() {
initialState = void 0, rootState = void 0, nextState = void 0, routeInfo = void 0, resetLinking(), navigationRefSubscription?.(), rootStateSubscribers.clear(), storeSubscribers.clear();
}
function setupLinkingAndRouteInfo(initialLocation) {
initialState = setupLinking(routeNode, initialLocation), initialState ? (rootState = initialState, routeInfo = getRouteInfo(initialState)) : routeInfo = {
unstable_globalHref: "",
pathname: "",
isIndex: !1,
params: {},
segments: []
};
}
function subscribeToNavigationChanges() {
navigationRefSubscription = navigationRef.addListener("state", (data) => {
let state = { ...data.data.state };
state.key && hashes[state.key] && (state.hash = hashes[state.key], delete hashes[state.key]), hasAttemptedToHideSplash || (hasAttemptedToHideSplash = !0, splashScreenAnimationFrame = requestAnimationFrame(() => {
})), nextOptions && (state = { ...state, linkOptions: nextOptions }, nextOptions = null);
let shouldUpdateSubscribers = nextState === state;
nextState = void 0, state && state !== rootState && (updateState(state, void 0), shouldUpdateSubscribers = !0), shouldUpdateSubscribers && startTransition(() => {
for (const subscriber of rootStateSubscribers)
subscriber(state);
});
}), startTransition(() => {
updateSnapshot();
for (const subscriber of storeSubscribers)
subscriber();
});
}
function navigate(url, options) {
return linkTo(resolveHref(url), "NAVIGATE", options);
}
function push(url, options) {
return linkTo(resolveHref(url), "PUSH", options);
}
function dismiss(count) {
process.env.ONE_DEBUG_ROUTER && console.info(`[one] \u{1F519} dismiss${count ? ` (${count})` : ""}`), navigationRef?.dispatch(StackActions.pop(count));
}
function replace(url, options) {
return linkTo(resolveHref(url), "REPLACE", options);
}
function setParams(params = {}) {
return assertIsReady(navigationRef), navigationRef?.current?.setParams(
// @ts-expect-error
params
);
}
function dismissAll() {
process.env.ONE_DEBUG_ROUTER && console.info("[one] \u{1F519} dismissAll"), navigationRef?.dispatch(StackActions.popToTop());
}
function goBack() {
process.env.ONE_DEBUG_ROUTER && console.info("[one] \u{1F519} goBack"), assertIsReady(navigationRef), navigationRef?.current?.goBack();
}
function canGoBack() {
return navigationRef.isReady() ? navigationRef?.current?.canGoBack() ?? !1 : !1;
}
function canDismiss() {
let state = rootState;
for (; state; ) {
if (state.type === "stack" && state.routes.length > 1)
return !0;
if (state.index === void 0)
return !1;
state = state.routes?.[state.index]?.state;
}
return !1;
}
function getSortedRoutes() {
if (!routeNode)
throw new Error("No routes");
return routeNode.children.filter((route) => !route.internal).sort(sortRoutes);
}
function updateState(state, nextStateParam = state) {
rootState = state, nextState = nextStateParam;
const nextRouteInfo = getRouteInfo(state);
if (!deepEqual(routeInfo, nextRouteInfo)) {
if (process.env.ONE_DEBUG_ROUTER) {
const from = routeInfo?.pathname || "(initial)", to = nextRouteInfo.pathname, params = Object.keys(nextRouteInfo.params || {}).length ? nextRouteInfo.params : void 0;
console.info(`[one] \u{1F9ED} ${from} \u2192 ${to}`, params ? { params } : "");
}
routeInfo = nextRouteInfo;
}
}
function subscribeToRootState(subscriber) {
return rootStateSubscribers.add(subscriber), () => {
rootStateSubscribers.delete(subscriber);
};
}
function subscribeToStore(subscriber) {
return storeSubscribers.add(subscriber), () => {
storeSubscribers.delete(subscriber);
};
}
function subscribeToLoadingState(subscriber) {
return loadingStateSubscribers.add(subscriber), () => {
loadingStateSubscribers.delete(subscriber);
};
}
function setLoadingState(state) {
startTransition(() => {
for (const listener of loadingStateSubscribers)
listener(state);
});
}
let currentSnapshot = null;
function updateSnapshot() {
currentSnapshot = getSnapshot();
}
function snapshot() {
return currentSnapshot;
}
function getSnapshot() {
return {
linkTo,
routeNode,
rootComponent,
linking: getLinking(),
hasAttemptedToHideSplash,
initialState,
rootState,
nextState,
routeInfo,
splashScreenAnimationFrame,
navigationRef,
navigationRefSubscription,
rootStateSubscribers,
storeSubscribers
};
}
function rootStateSnapshot() {
return rootState;
}
function routeInfoSnapshot() {
return routeInfo;
}
function useOneRouter() {
const state = useSyncExternalStore(subscribeToStore, snapshot, snapshot);
return useDeferredValue(state);
}
function syncStoreRootState() {
if (!navigationRef)
throw new Error("No navigationRef, possible duplicate One dep");
if (navigationRef.isReady()) {
const currentState = navigationRef.getRootState();
rootState !== currentState && updateState(currentState);
}
}
function useStoreRootState() {
syncStoreRootState();
const state = useSyncExternalStore(
subscribeToRootState,
rootStateSnapshot,
rootStateSnapshot
);
return useDeferredValue(state);
}
function useStoreRouteInfo() {
syncStoreRootState();
const state = useSyncExternalStore(
subscribeToRootState,
routeInfoSnapshot,
routeInfoSnapshot
);
return useDeferredValue(state);
}
function cleanup() {
splashScreenAnimationFrame && cancelAnimationFrame(splashScreenAnimationFrame);
}
const preloadingLoader = {};
async function doPreload(href) {
const preloadPath = getPreloadPath(href), loaderPath = getLoaderPath(href), cssPreloadPath = getPreloadCSSPath(href);
try {
const [_preload, cssPreloadModule, loader] = await Promise.all([
dynamicImport(preloadPath),
dynamicImport(cssPreloadPath)?.catch(() => null) ?? Promise.resolve(null),
// graceful fail if no CSS preload
dynamicImport(loaderPath)?.catch(() => null) ?? Promise.resolve(null),
// graceful fail if no loader file
preloadRouteModules(href)
]);
return cssPreloadModule?.injectCSS && (cssInjectFunctions[href] = cssPreloadModule.injectCSS), loader?.loader ? await loader.loader() ?? null : null;
} catch (err) {
return console.error(`[one] preload error for ${href}:`, err), null;
}
}
const preloadedLoaderData = {}, cssInjectFunctions = {};
function preloadRoute(href, injectCSS = !1) {
if (process.env.NODE_ENV !== "development")
return preloadingLoader[href] || (preloadingLoader[href] = doPreload(href).then((data) => (preloadedLoaderData[href] = data, data))), injectCSS ? preloadingLoader[href]?.then(async (data) => {
const inject = cssInjectFunctions[href];
return inject && await Promise.race([inject(), new Promise((r) => setTimeout(r, 500))]), data;
}) : preloadingLoader[href];
}
async function linkTo(href, event, options) {
if (process.env.ONE_DEBUG_ROUTER && console.info(`[one] \u{1F517} ${event || "NAVIGATE"} ${href}`), href[0] === "#")
return;
if (shouldLinkExternally(href)) {
openExternalURL(href);
return;
}
assertIsReady(navigationRef);
const current = navigationRef.current;
if (current == null)
throw new Error(
"Couldn't find a navigation object. Is your component inside NavigationContainer?"
);
const linking = getLinking();
if (!linking)
throw new Error("Attempted to link to route when no routes are present");
if (setLastAction(), href === ".." || href === "../") {
current.goBack();
return;
}
if (href.startsWith(".")) {
let base = routeInfo?.segments?.map((segment) => {
if (!segment.startsWith("[")) return segment;
if (segment.startsWith("[...")) {
segment = segment.slice(4, -1);
const params = routeInfo?.params?.[segment];
return Array.isArray(params) ? params.join("/") : params?.split(",")?.join("/") ?? "";
}
return segment = segment.slice(1, -1), routeInfo?.params?.[segment];
}).filter(Boolean).join("/") ?? "/";
routeInfo?.isIndex || (base += "/.."), href = resolve(base, href);
}
const state = linking.getStateFromPath(href, linking.config);
if (!state || state.routes.length === 0) {
console.error(
"Could not generate a valid navigation state for the given path: " + href
), console.error("linking.config", linking.config), console.error("routes", getSortedRoutes());
return;
}
setLoadingState("loading"), await preloadRoute(href, !0);
const rootState2 = navigationRef.getRootState(), hash = href.indexOf("#");
rootState2.key && hash > 0 && (hashes[rootState2.key] = href.slice(hash)), nextOptions = options ?? null, startTransition(() => {
const action = getNavigateAction(state, rootState2, event), current2 = navigationRef.getCurrentRoute();
navigationRef.dispatch(action);
let warningTm;
const interval = setInterval(() => {
const next = navigationRef.getCurrentRoute();
current2 !== next && setTimeout(() => {
setLoadingState("loaded");
}), clearTimeout(warningTm), clearTimeout(interval);
}, 16);
process.env.NODE_ENV === "development" && (warningTm = setTimeout(() => {
console.warn("Routing took more than 8 seconds");
}, 1e3));
});
}
const hashes = {};
let nextOptions = null;
function deepEqual(a, b) {
if (a === b)
return !0;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length)
return !1;
for (let i = 0; i < a.length; i++)
if (!deepEqual(a[i], b[i]))
return !1;
return !0;
}
if (typeof a == "object" && typeof b == "object") {
const keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length !== keysB.length)
return !1;
for (const key of keysA)
if (!deepEqual(a[key], b[key]))
return !1;
return !0;
}
return !1;
}
export {
canDismiss,
canGoBack,
cleanup,
dismiss,
dismissAll,
getSortedRoutes,
goBack,
hasAttemptedToHideSplash,
initialState,
initialize,
linkTo,
navigate,
navigationRef,
preloadRoute,
preloadedLoaderData,
preloadingLoader,
push,
replace,
rootComponent,
rootState,
rootStateSnapshot,
routeInfo,
routeInfoSnapshot,
routeNode,
setLoadingState,
setParams,
snapshot,
subscribeToLoadingState,
subscribeToRootState,
subscribeToStore,
updateState,
useOneRouter,
useStoreRootState,
useStoreRouteInfo
};
//# sourceMappingURL=router.js.map