expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
229 lines • 9.21 kB
JavaScript
'use client';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.store = void 0;
exports.useStore = useStore;
exports.useRouteInfo = useRouteInfo;
const native_1 = require("@react-navigation/native");
const expo_constants_1 = __importDefault(require("expo-constants"));
const react_1 = require("react");
const react_native_1 = require("react-native");
const extractPathFromURL_1 = require("../fork/extractPathFromURL");
const getStateFromPath_forks_1 = require("../fork/getStateFromPath-forks");
const getLinkingConfig_1 = require("../getLinkingConfig");
const getReactNavigationConfig_1 = require("../getReactNavigationConfig");
const getRoutes_1 = require("../getRoutes");
const routeInfo_1 = require("./routeInfo");
const useScreens_1 = require("../useScreens");
const url_1 = require("../utils/url");
const SplashScreen = __importStar(require("../views/Splash"));
const storeRef = {
current: {},
};
const routeInfoCache = new WeakMap();
let splashScreenAnimationFrame;
let hasAttemptedToHideSplash = false;
exports.store = {
shouldShowTutorial() {
return !storeRef.current.routeNode && process.env.NODE_ENV === 'development';
},
get state() {
return storeRef.current.state;
},
get navigationRef() {
return storeRef.current.navigationRef;
},
get routeNode() {
return storeRef.current.routeNode;
},
getRouteInfo() {
return storeRef.current.routeInfo || routeInfo_1.defaultRouteInfo;
},
get redirects() {
return storeRef.current.redirects || [];
},
get rootComponent() {
return storeRef.current.rootComponent;
},
get linking() {
return storeRef.current.linking;
},
setFocusedState(state) {
const routeInfo = getCachedRouteInfo(state);
storeRef.current.routeInfo = routeInfo;
},
onReady() {
if (!hasAttemptedToHideSplash) {
hasAttemptedToHideSplash = true;
// NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially.
splashScreenAnimationFrame = requestAnimationFrame(() => {
SplashScreen._internal_maybeHideAsync?.();
});
}
storeRef.current.navigationRef.addListener('state', (e) => {
if (!e.data.state) {
return;
}
let isStale = false;
let state = e.data.state;
while (!isStale && state) {
isStale = state.stale;
state =
state.routes?.['index' in state && typeof state.index === 'number'
? state.index
: state.routes.length - 1]?.state;
}
storeRef.current.state = e.data.state;
if (!isStale) {
storeRef.current.routeInfo = getCachedRouteInfo(e.data.state);
}
for (const callback of routeInfoSubscribers) {
callback();
}
});
},
assertIsReady() {
if (!storeRef.current.navigationRef.isReady()) {
throw new Error('Attempted to navigate before mounting the Root Layout component. Ensure the Root Layout component is rendering a Slot, or other navigator on the first render.');
}
},
};
function useStore(context, linkingConfigOptions, serverUrl) {
const navigationRef = (0, native_1.useNavigationContainerRef)();
const config = expo_constants_1.default.expoConfig?.extra?.router;
let linking;
let rootComponent = react_1.Fragment;
let initialState;
const routeNode = (0, getRoutes_1.getRoutes)(context, {
...config,
ignoreEntryPoints: true,
platform: react_native_1.Platform.OS,
});
const redirects = [config?.redirects, config?.rewrites]
.filter(Boolean)
.flat()
.map((route) => {
return [
(0, getStateFromPath_forks_1.routePatternToRegex)((0, getReactNavigationConfig_1.parseRouteSegments)(route.source)),
route,
(0, url_1.shouldLinkExternally)(route.destination),
];
});
if (routeNode) {
// We have routes, so get the linking config and the root component
linking = (0, getLinkingConfig_1.getLinkingConfig)(routeNode, context, () => exports.store.getRouteInfo(), {
metaOnly: linkingConfigOptions.metaOnly,
serverUrl,
redirects,
});
rootComponent = (0, useScreens_1.getQualifiedRouteComponent)(routeNode);
// By default React Navigation is async and does not render anything in the first pass as it waits for `getInitialURL`
// This will cause static rendering to fail, which once performs a single pass.
// If the initialURL is a string, we can prefetch the state and routeInfo, skipping React Navigation's async behavior.
const initialURL = linking?.getInitialURL?.();
if (typeof initialURL === 'string') {
let initialPath = (0, extractPathFromURL_1.extractExpoPathFromURL)(linking.prefixes, initialURL);
// It does not matter if the path starts with a `/` or not, but this keeps the behavior consistent
if (!initialPath.startsWith('/'))
initialPath = '/' + initialPath;
initialState = linking.getStateFromPath(initialPath, linking.config);
const initialRouteInfo = (0, routeInfo_1.getRouteInfoFromState)(initialState);
routeInfoCache.set(initialState, initialRouteInfo);
}
}
else {
// Only error in production, in development we will show the onboarding screen
if (process.env.NODE_ENV === 'production') {
throw new Error('No routes found');
}
// In development, we will show the onboarding screen
rootComponent = react_1.Fragment;
}
storeRef.current = {
navigationRef,
routeNode,
config,
rootComponent,
linking,
redirects,
state: initialState,
};
if (initialState) {
storeRef.current.routeInfo = getCachedRouteInfo(initialState);
}
(0, react_1.useEffect)(() => {
return () => {
// listener();
if (splashScreenAnimationFrame) {
cancelAnimationFrame(splashScreenAnimationFrame);
splashScreenAnimationFrame = undefined;
}
};
});
return exports.store;
}
const routeInfoSubscribers = new Set();
const routeInfoSubscribe = (callback) => {
routeInfoSubscribers.add(callback);
return () => {
routeInfoSubscribers.delete(callback);
};
};
function useRouteInfo() {
return (0, react_1.useSyncExternalStore)(routeInfoSubscribe, exports.store.getRouteInfo, exports.store.getRouteInfo);
}
function getCachedRouteInfo(state) {
let routeInfo = routeInfoCache.get(state);
if (!routeInfo) {
routeInfo = (0, routeInfo_1.getRouteInfoFromState)(state);
const previousRouteInfo = storeRef.current.routeInfo;
if (previousRouteInfo) {
const areEqual = routeInfo.segments.length === previousRouteInfo.segments.length &&
routeInfo.segments.every((segment, index) => previousRouteInfo.segments[index] === segment) &&
routeInfo.pathnameWithParams === previousRouteInfo.pathnameWithParams;
if (areEqual) {
// If they are equal, keep the previous route info for object reference equality
routeInfo = previousRouteInfo;
}
}
routeInfoCache.set(state, routeInfo);
}
return routeInfo;
}
//# sourceMappingURL=router-store.js.map
;