Spaces:
Running
Running
| ; | |
| Object.defineProperty(exports, "__esModule", { | |
| value: true | |
| }); | |
| Object.defineProperty(exports, "default", { | |
| enumerable: true, | |
| get: function() { | |
| return AppRouter; | |
| } | |
| }); | |
| const _interop_require_default = require("@swc/helpers/_/_interop_require_default"); | |
| const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard"); | |
| const _jsxruntime = require("react/jsx-runtime"); | |
| const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react")); | |
| const _approutercontextsharedruntime = require("../../shared/lib/app-router-context.shared-runtime"); | |
| const _routerreducertypes = require("./router-reducer/router-reducer-types"); | |
| const _createhreffromurl = require("./router-reducer/create-href-from-url"); | |
| const _hooksclientcontextsharedruntime = require("../../shared/lib/hooks-client-context.shared-runtime"); | |
| const _useactionqueue = require("./use-action-queue"); | |
| const _approuterannouncer = require("./app-router-announcer"); | |
| const _redirectboundary = require("./redirect-boundary"); | |
| const _findheadincache = require("./router-reducer/reducers/find-head-in-cache"); | |
| const _unresolvedthenable = require("./unresolved-thenable"); | |
| const _removebasepath = require("../remove-base-path"); | |
| const _hasbasepath = require("../has-base-path"); | |
| const _computechangedpath = require("./router-reducer/compute-changed-path"); | |
| const _navfailurehandler = require("./nav-failure-handler"); | |
| const _approuterinstance = require("./app-router-instance"); | |
| const _redirect = require("./redirect"); | |
| const _redirecterror = require("./redirect-error"); | |
| const _links = require("./links"); | |
| const _rooterrorboundary = /*#__PURE__*/ _interop_require_default._(require("./errors/root-error-boundary")); | |
| const _globalerror = /*#__PURE__*/ _interop_require_default._(require("./builtin/global-error")); | |
| const _boundarycomponents = require("../../lib/framework/boundary-components"); | |
| const _deploymentid = require("../../shared/lib/deployment-id"); | |
| const globalMutable = {}; | |
| function HistoryUpdater({ appRouterState }) { | |
| (0, _react.useInsertionEffect)(()=>{ | |
| if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) { | |
| // clear pending URL as navigation is no longer | |
| // in flight | |
| window.next.__pendingUrl = undefined; | |
| } | |
| const { tree, pushRef, canonicalUrl, renderedSearch } = appRouterState; | |
| const appHistoryState = { | |
| tree, | |
| renderedSearch | |
| }; | |
| // TODO: Use Navigation API if available | |
| const historyState = { | |
| ...pushRef.preserveCustomHistoryState ? window.history.state : {}, | |
| // Identifier is shortened intentionally. | |
| // __NA is used to identify if the history entry can be handled by the app-router. | |
| // __N is used to identify if the history entry can be handled by the old router. | |
| __NA: true, | |
| __PRIVATE_NEXTJS_INTERNALS_TREE: appHistoryState | |
| }; | |
| if (pushRef.pendingPush && // Skip pushing an additional history entry if the canonicalUrl is the same as the current url. | |
| // This mirrors the browser behavior for normal navigation. | |
| (0, _createhreffromurl.createHrefFromUrl)(new URL(window.location.href)) !== canonicalUrl) { | |
| // This intentionally mutates React state, pushRef is overwritten to ensure additional push/replace calls do not trigger an additional history entry. | |
| pushRef.pendingPush = false; | |
| window.history.pushState(historyState, '', canonicalUrl); | |
| } else { | |
| window.history.replaceState(historyState, '', canonicalUrl); | |
| } | |
| }, [ | |
| appRouterState | |
| ]); | |
| (0, _react.useEffect)(()=>{ | |
| // The Next-Url and the base tree may affect the result of a prefetch | |
| // task. Re-prefetch all visible links with the updated values. In most | |
| // cases, this will not result in any new network requests, only if | |
| // the prefetch result actually varies on one of these inputs. | |
| (0, _links.pingVisibleLinks)(appRouterState.nextUrl, appRouterState.tree); | |
| }, [ | |
| appRouterState.nextUrl, | |
| appRouterState.tree | |
| ]); | |
| return null; | |
| } | |
| function copyNextJsInternalHistoryState(data) { | |
| if (data == null) data = {}; | |
| const currentState = window.history.state; | |
| const __NA = currentState?.__NA; | |
| if (__NA) { | |
| data.__NA = __NA; | |
| } | |
| const __PRIVATE_NEXTJS_INTERNALS_TREE = currentState?.__PRIVATE_NEXTJS_INTERNALS_TREE; | |
| if (__PRIVATE_NEXTJS_INTERNALS_TREE) { | |
| data.__PRIVATE_NEXTJS_INTERNALS_TREE = __PRIVATE_NEXTJS_INTERNALS_TREE; | |
| } | |
| return data; | |
| } | |
| function Head({ headCacheNode }) { | |
| // If this segment has a `prefetchHead`, it's the statically prefetched data. | |
| // We should use that on initial render instead of `head`. Then we'll switch | |
| // to `head` when the dynamic response streams in. | |
| const head = headCacheNode !== null ? headCacheNode.head : null; | |
| const prefetchHead = headCacheNode !== null ? headCacheNode.prefetchHead : null; | |
| // If no prefetch data is available, then we go straight to rendering `head`. | |
| const resolvedPrefetchRsc = prefetchHead !== null ? prefetchHead : head; | |
| // We use `useDeferredValue` to handle switching between the prefetched and | |
| // final values. The second argument is returned on initial render, then it | |
| // re-renders with the first argument. | |
| return (0, _react.useDeferredValue)(head, resolvedPrefetchRsc); | |
| } | |
| /** | |
| * The global router that wraps the application components. | |
| */ function Router({ actionQueue, globalError, webSocket, staticIndicatorState }) { | |
| const state = (0, _useactionqueue.useActionQueue)(actionQueue); | |
| const { canonicalUrl } = state; | |
| // Add memoized pathname/query for useSearchParams and usePathname. | |
| const { searchParams, pathname } = (0, _react.useMemo)(()=>{ | |
| const url = new URL(canonicalUrl, typeof window === 'undefined' ? 'http://n' : window.location.href); | |
| return { | |
| // This is turned into a readonly class in `useSearchParams` | |
| searchParams: url.searchParams, | |
| pathname: (0, _hasbasepath.hasBasePath)(url.pathname) ? (0, _removebasepath.removeBasePath)(url.pathname) : url.pathname | |
| }; | |
| }, [ | |
| canonicalUrl | |
| ]); | |
| if (process.env.NODE_ENV !== 'production') { | |
| const { cache, tree } = state; | |
| // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes | |
| // eslint-disable-next-line react-hooks/rules-of-hooks | |
| (0, _react.useEffect)(()=>{ | |
| // Add `window.nd` for debugging purposes. | |
| // This is not meant for use in applications as concurrent rendering will affect the cache/tree/router. | |
| // @ts-ignore this is for debugging | |
| window.nd = { | |
| router: _approuterinstance.publicAppRouterInstance, | |
| cache, | |
| tree | |
| }; | |
| }, [ | |
| cache, | |
| tree | |
| ]); | |
| } | |
| (0, _react.useEffect)(()=>{ | |
| // If the app is restored from bfcache, it's possible that | |
| // pushRef.mpaNavigation is true, which would mean that any re-render of this component | |
| // would trigger the mpa navigation logic again from the lines below. | |
| // This will restore the router to the initial state in the event that the app is restored from bfcache. | |
| function handlePageShow(event) { | |
| if (!event.persisted || !window.history.state?.__PRIVATE_NEXTJS_INTERNALS_TREE) { | |
| return; | |
| } | |
| // Clear the pendingMpaPath value so that a subsequent MPA navigation to the same URL can be triggered. | |
| // This is necessary because if the browser restored from bfcache, the pendingMpaPath would still be set to the value | |
| // of the last MPA navigation. | |
| globalMutable.pendingMpaPath = undefined; | |
| (0, _useactionqueue.dispatchAppRouterAction)({ | |
| type: _routerreducertypes.ACTION_RESTORE, | |
| url: new URL(window.location.href), | |
| historyState: window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE | |
| }); | |
| } | |
| window.addEventListener('pageshow', handlePageShow); | |
| return ()=>{ | |
| window.removeEventListener('pageshow', handlePageShow); | |
| }; | |
| }, []); | |
| (0, _react.useEffect)(()=>{ | |
| // Ensure that any redirect errors that bubble up outside of the RedirectBoundary | |
| // are caught and handled by the router. | |
| function handleUnhandledRedirect(event) { | |
| const error = 'reason' in event ? event.reason : event.error; | |
| if ((0, _redirecterror.isRedirectError)(error)) { | |
| event.preventDefault(); | |
| const url = (0, _redirect.getURLFromRedirectError)(error); | |
| const redirectType = (0, _redirect.getRedirectTypeFromError)(error); | |
| // TODO: This should access the router methods directly, rather than | |
| // go through the public interface. | |
| if (redirectType === _redirecterror.RedirectType.push) { | |
| _approuterinstance.publicAppRouterInstance.push(url, {}); | |
| } else { | |
| _approuterinstance.publicAppRouterInstance.replace(url, {}); | |
| } | |
| } | |
| } | |
| window.addEventListener('error', handleUnhandledRedirect); | |
| window.addEventListener('unhandledrejection', handleUnhandledRedirect); | |
| return ()=>{ | |
| window.removeEventListener('error', handleUnhandledRedirect); | |
| window.removeEventListener('unhandledrejection', handleUnhandledRedirect); | |
| }; | |
| }, []); | |
| // When mpaNavigation flag is set do a hard navigation to the new url. | |
| // Infinitely suspend because we don't actually want to rerender any child | |
| // components with the new URL and any entangled state updates shouldn't | |
| // commit either (eg: useTransition isPending should stay true until the page | |
| // unloads). | |
| // | |
| // This is a side effect in render. Don't try this at home, kids. It's | |
| // probably safe because we know this is a singleton component and it's never | |
| // in <Offscreen>. At least I hope so. (It will run twice in dev strict mode, | |
| // but that's... fine?) | |
| const { pushRef } = state; | |
| if (pushRef.mpaNavigation) { | |
| // if there's a re-render, we don't want to trigger another redirect if one is already in flight to the same URL | |
| if (globalMutable.pendingMpaPath !== canonicalUrl) { | |
| const location = window.location; | |
| if (pushRef.pendingPush) { | |
| location.assign(canonicalUrl); | |
| } else { | |
| location.replace(canonicalUrl); | |
| } | |
| globalMutable.pendingMpaPath = canonicalUrl; | |
| } | |
| // TODO-APP: Should we listen to navigateerror here to catch failed | |
| // navigations somehow? And should we call window.stop() if a SPA navigation | |
| // should interrupt an MPA one? | |
| // NOTE: This is intentionally using `throw` instead of `use` because we're | |
| // inside an externally mutable condition (pushRef.mpaNavigation), which | |
| // violates the rules of hooks. | |
| throw _unresolvedthenable.unresolvedThenable; | |
| } | |
| (0, _react.useEffect)(()=>{ | |
| const originalPushState = window.history.pushState.bind(window.history); | |
| const originalReplaceState = window.history.replaceState.bind(window.history); | |
| // Ensure the canonical URL in the Next.js Router is updated when the URL is changed so that `usePathname` and `useSearchParams` hold the pushed values. | |
| const applyUrlFromHistoryPushReplace = (url)=>{ | |
| const href = window.location.href; | |
| const appHistoryState = window.history.state?.__PRIVATE_NEXTJS_INTERNALS_TREE; | |
| (0, _react.startTransition)(()=>{ | |
| (0, _useactionqueue.dispatchAppRouterAction)({ | |
| type: _routerreducertypes.ACTION_RESTORE, | |
| url: new URL(url ?? href, href), | |
| historyState: appHistoryState | |
| }); | |
| }); | |
| }; | |
| /** | |
| * Patch pushState to ensure external changes to the history are reflected in the Next.js Router. | |
| * Ensures Next.js internal history state is copied to the new history entry. | |
| * Ensures usePathname and useSearchParams hold the newly provided url. | |
| */ window.history.pushState = function pushState(data, _unused, url) { | |
| // TODO: Warn when Navigation API is available (navigation.navigate() should be used) | |
| // Avoid a loop when Next.js internals trigger pushState/replaceState | |
| if (data?.__NA || data?._N) { | |
| return originalPushState(data, _unused, url); | |
| } | |
| data = copyNextJsInternalHistoryState(data); | |
| if (url) { | |
| applyUrlFromHistoryPushReplace(url); | |
| } | |
| return originalPushState(data, _unused, url); | |
| }; | |
| /** | |
| * Patch replaceState to ensure external changes to the history are reflected in the Next.js Router. | |
| * Ensures Next.js internal history state is copied to the new history entry. | |
| * Ensures usePathname and useSearchParams hold the newly provided url. | |
| */ window.history.replaceState = function replaceState(data, _unused, url) { | |
| // TODO: Warn when Navigation API is available (navigation.navigate() should be used) | |
| // Avoid a loop when Next.js internals trigger pushState/replaceState | |
| if (data?.__NA || data?._N) { | |
| return originalReplaceState(data, _unused, url); | |
| } | |
| data = copyNextJsInternalHistoryState(data); | |
| if (url) { | |
| applyUrlFromHistoryPushReplace(url); | |
| } | |
| return originalReplaceState(data, _unused, url); | |
| }; | |
| /** | |
| * Handle popstate event, this is used to handle back/forward in the browser. | |
| * By default dispatches ACTION_RESTORE, however if the history entry was not pushed/replaced by app-router it will reload the page. | |
| * That case can happen when the old router injected the history entry. | |
| */ const onPopState = (event)=>{ | |
| if (!event.state) { | |
| // TODO-APP: this case only happens when pushState/replaceState was called outside of Next.js. It should probably reload the page in this case. | |
| return; | |
| } | |
| // This case happens when the history entry was pushed by the `pages` router. | |
| if (!event.state.__NA) { | |
| window.location.reload(); | |
| return; | |
| } | |
| // TODO-APP: Ideally the back button should not use startTransition as it should apply the updates synchronously | |
| // Without startTransition works if the cache is there for this path | |
| (0, _react.startTransition)(()=>{ | |
| (0, _approuterinstance.dispatchTraverseAction)(window.location.href, event.state.__PRIVATE_NEXTJS_INTERNALS_TREE); | |
| }); | |
| }; | |
| // Register popstate event to call onPopstate. | |
| window.addEventListener('popstate', onPopState); | |
| return ()=>{ | |
| window.history.pushState = originalPushState; | |
| window.history.replaceState = originalReplaceState; | |
| window.removeEventListener('popstate', onPopState); | |
| }; | |
| }, []); | |
| const { cache, tree, nextUrl, focusAndScrollRef, previousNextUrl } = state; | |
| const matchingHead = (0, _react.useMemo)(()=>{ | |
| return (0, _findheadincache.findHeadInCache)(cache, tree[1]); | |
| }, [ | |
| cache, | |
| tree | |
| ]); | |
| // Add memoized pathParams for useParams. | |
| const pathParams = (0, _react.useMemo)(()=>{ | |
| return (0, _computechangedpath.getSelectedParams)(tree); | |
| }, [ | |
| tree | |
| ]); | |
| // Create instrumented promises for navigation hooks (dev-only) | |
| // These are specially instrumented promises to show in the Suspense DevTools | |
| // Promises are cached outside of render to survive suspense retries. | |
| let instrumentedNavigationPromises = null; | |
| if (process.env.NODE_ENV !== 'production') { | |
| const { createRootNavigationPromises } = require('./navigation-devtools'); | |
| instrumentedNavigationPromises = createRootNavigationPromises(tree, pathname, searchParams, pathParams); | |
| } | |
| const layoutRouterContext = (0, _react.useMemo)(()=>{ | |
| return { | |
| parentTree: tree, | |
| parentCacheNode: cache, | |
| parentSegmentPath: null, | |
| parentParams: {}, | |
| // This is the <Activity> "name" that shows up in the Suspense DevTools. | |
| // It represents the root of the app. | |
| debugNameContext: '/', | |
| // Root node always has `url` | |
| // Provided in AppTreeContext to ensure it can be overwritten in layout-router | |
| url: canonicalUrl, | |
| // Root segment is always active | |
| isActive: true | |
| }; | |
| }, [ | |
| tree, | |
| cache, | |
| canonicalUrl | |
| ]); | |
| const globalLayoutRouterContext = (0, _react.useMemo)(()=>{ | |
| return { | |
| tree, | |
| focusAndScrollRef, | |
| nextUrl, | |
| previousNextUrl | |
| }; | |
| }, [ | |
| tree, | |
| focusAndScrollRef, | |
| nextUrl, | |
| previousNextUrl | |
| ]); | |
| let head; | |
| if (matchingHead !== null) { | |
| // The head is wrapped in an extra component so we can use | |
| // `useDeferredValue` to swap between the prefetched and final versions of | |
| // the head. (This is what LayoutRouter does for segment data, too.) | |
| // | |
| // The `key` is used to remount the component whenever the head moves to | |
| // a different segment. | |
| const [headCacheNode, headKey, headKeyWithoutSearchParams] = matchingHead; | |
| head = /*#__PURE__*/ (0, _jsxruntime.jsx)(Head, { | |
| headCacheNode: headCacheNode | |
| }, // Necessary for PPR: omit search params from the key to match prerendered keys | |
| typeof window === 'undefined' ? headKeyWithoutSearchParams : headKey); | |
| } else { | |
| head = null; | |
| } | |
| let content = /*#__PURE__*/ (0, _jsxruntime.jsxs)(_redirectboundary.RedirectBoundary, { | |
| children: [ | |
| head, | |
| /*#__PURE__*/ (0, _jsxruntime.jsx)(_boundarycomponents.RootLayoutBoundary, { | |
| children: cache.rsc | |
| }), | |
| /*#__PURE__*/ (0, _jsxruntime.jsx)(_approuterannouncer.AppRouterAnnouncer, { | |
| tree: tree | |
| }) | |
| ] | |
| }); | |
| if (process.env.NODE_ENV !== 'production') { | |
| // In development, we apply few error boundaries and hot-reloader: | |
| // - DevRootHTTPAccessFallbackBoundary: avoid using navigation API like notFound() in root layout | |
| // - HotReloader: | |
| // - hot-reload the app when the code changes | |
| // - render dev overlay | |
| // - catch runtime errors and display global-error when necessary | |
| if (typeof window !== 'undefined') { | |
| const { DevRootHTTPAccessFallbackBoundary } = require('./dev-root-http-access-fallback-boundary'); | |
| content = /*#__PURE__*/ (0, _jsxruntime.jsx)(DevRootHTTPAccessFallbackBoundary, { | |
| children: content | |
| }); | |
| } | |
| const HotReloader = require('../dev/hot-reloader/app/hot-reloader-app').default; | |
| content = /*#__PURE__*/ (0, _jsxruntime.jsx)(HotReloader, { | |
| globalError: globalError, | |
| webSocket: webSocket, | |
| staticIndicatorState: staticIndicatorState, | |
| children: content | |
| }); | |
| } else { | |
| content = /*#__PURE__*/ (0, _jsxruntime.jsx)(_rooterrorboundary.default, { | |
| errorComponent: globalError[0], | |
| errorStyles: globalError[1], | |
| children: content | |
| }); | |
| } | |
| return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, { | |
| children: [ | |
| /*#__PURE__*/ (0, _jsxruntime.jsx)(HistoryUpdater, { | |
| appRouterState: state | |
| }), | |
| /*#__PURE__*/ (0, _jsxruntime.jsx)(RuntimeStyles, {}), | |
| /*#__PURE__*/ (0, _jsxruntime.jsx)(_hooksclientcontextsharedruntime.NavigationPromisesContext.Provider, { | |
| value: instrumentedNavigationPromises, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_hooksclientcontextsharedruntime.PathParamsContext.Provider, { | |
| value: pathParams, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_hooksclientcontextsharedruntime.PathnameContext.Provider, { | |
| value: pathname, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_hooksclientcontextsharedruntime.SearchParamsContext.Provider, { | |
| value: searchParams, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_approutercontextsharedruntime.GlobalLayoutRouterContext.Provider, { | |
| value: globalLayoutRouterContext, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_approutercontextsharedruntime.AppRouterContext.Provider, { | |
| value: _approuterinstance.publicAppRouterInstance, | |
| children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_approutercontextsharedruntime.LayoutRouterContext.Provider, { | |
| value: layoutRouterContext, | |
| children: content | |
| }) | |
| }) | |
| }) | |
| }) | |
| }) | |
| }) | |
| }) | |
| ] | |
| }); | |
| } | |
| function AppRouter({ actionQueue, globalErrorState, webSocket, staticIndicatorState }) { | |
| (0, _navfailurehandler.useNavFailureHandler)(); | |
| const router = /*#__PURE__*/ (0, _jsxruntime.jsx)(Router, { | |
| actionQueue: actionQueue, | |
| globalError: globalErrorState, | |
| webSocket: webSocket, | |
| staticIndicatorState: staticIndicatorState | |
| }); | |
| // At the very top level, use the default GlobalError component as the final fallback. | |
| // When the app router itself fails, which means the framework itself fails, we show the default error. | |
| return /*#__PURE__*/ (0, _jsxruntime.jsx)(_rooterrorboundary.default, { | |
| errorComponent: _globalerror.default, | |
| children: router | |
| }); | |
| } | |
| const runtimeStyles = new Set(); | |
| let runtimeStyleChanged = new Set(); | |
| globalThis._N_E_STYLE_LOAD = function(href) { | |
| let len = runtimeStyles.size; | |
| runtimeStyles.add(href); | |
| if (runtimeStyles.size !== len) { | |
| runtimeStyleChanged.forEach((cb)=>cb()); | |
| } | |
| // TODO figure out how to get a promise here | |
| // But maybe it's not necessary as react would block rendering until it's loaded | |
| return Promise.resolve(); | |
| }; | |
| function RuntimeStyles() { | |
| const [, forceUpdate] = _react.default.useState(0); | |
| const renderedStylesSize = runtimeStyles.size; | |
| (0, _react.useEffect)(()=>{ | |
| const changed = ()=>forceUpdate((c)=>c + 1); | |
| runtimeStyleChanged.add(changed); | |
| if (renderedStylesSize !== runtimeStyles.size) { | |
| changed(); | |
| } | |
| return ()=>{ | |
| runtimeStyleChanged.delete(changed); | |
| }; | |
| }, [ | |
| renderedStylesSize, | |
| forceUpdate | |
| ]); | |
| const dplId = (0, _deploymentid.getDeploymentIdQueryOrEmptyString)(); | |
| return [ | |
| ...runtimeStyles | |
| ].map((href, i)=>/*#__PURE__*/ (0, _jsxruntime.jsx)("link", { | |
| rel: "stylesheet", | |
| href: `${href}${dplId}`, | |
| // @ts-ignore | |
| precedence: "next" | |
| }, i)); | |
| } | |
| if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') { | |
| Object.defineProperty(exports.default, '__esModule', { value: true }); | |
| Object.assign(exports.default, exports); | |
| module.exports = exports.default; | |
| } | |
| //# sourceMappingURL=app-router.js.map |