UNPKG

next

Version:

The React Framework

536 lines (534 loc) • 24.4 kB
'use client'; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { createEmptyCacheNode: null, createPrefetchURL: null, default: null, isExternalURL: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { createEmptyCacheNode: function() { return createEmptyCacheNode; }, createPrefetchURL: function() { return createPrefetchURL; }, default: function() { return AppRouter; }, isExternalURL: function() { return isExternalURL; } }); 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 _errorboundary = /*#__PURE__*/ _interop_require_wildcard._(require("./error-boundary")); const _isbot = require("../../shared/lib/router/utils/is-bot"); const _addbasepath = require("../add-base-path"); 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 globalMutable = {}; function isExternalURL(url) { return url.origin !== window.location.origin; } function createPrefetchURL(href) { // Don't prefetch for bots as they don't navigate. if ((0, _isbot.isBot)(window.navigator.userAgent)) { return null; } let url; try { url = new URL((0, _addbasepath.addBasePath)(href), window.location.href); } catch (_) { // TODO: Does this need to throw or can we just console.error instead? Does // anyone rely on this throwing? (Seems unlikely.) throw Object.defineProperty(new Error("Cannot prefetch '" + href + "' because it cannot be converted to a URL."), "__NEXT_ERROR_CODE", { value: "E234", enumerable: false, configurable: true }); } // Don't prefetch during development (improves compilation performance) if (process.env.NODE_ENV === 'development') { return null; } // External urls can't be prefetched in the same way. if (isExternalURL(url)) { return null; } return url; } function HistoryUpdater(param) { let { appRouterState } = param; (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 } = appRouterState; 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: tree }; 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. if (process.env.__NEXT_CLIENT_SEGMENT_CACHE) { (0, _links.pingVisibleLinks)(appRouterState.nextUrl, appRouterState.tree); } }, [ appRouterState.nextUrl, appRouterState.tree ]); return null; } function createEmptyCacheNode() { return { lazyData: null, rsc: null, prefetchRsc: null, head: null, prefetchHead: null, parallelRoutes: new Map(), loading: null, navigatedAt: -1 }; } function copyNextJsInternalHistoryState(data) { if (data == null) data = {}; const currentState = window.history.state; const __NA = currentState == null ? void 0 : currentState.__NA; if (__NA) { data.__NA = __NA; } const __PRIVATE_NEXTJS_INTERNALS_TREE = currentState == null ? void 0 : currentState.__PRIVATE_NEXTJS_INTERNALS_TREE; if (__PRIVATE_NEXTJS_INTERNALS_TREE) { data.__PRIVATE_NEXTJS_INTERNALS_TREE = __PRIVATE_NEXTJS_INTERNALS_TREE; } return data; } function Head(param) { let { headCacheNode } = param; // 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(param) { let { actionQueue, assetPrefix, globalError } = param; 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') { // eslint-disable-next-line react-hooks/rules-of-hooks const { cache, prefetchCache, 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, prefetchCache, tree }; }, [ cache, prefetchCache, 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) { var _window_history_state; if (!event.persisted || !((_window_history_state = window.history.state) == null ? void 0 : _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), tree: 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? (0, _react.use)(_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)=>{ var _window_history_state; const href = window.location.href; const tree = (_window_history_state = window.history.state) == null ? void 0 : _window_history_state.__PRIVATE_NEXTJS_INTERNALS_TREE; (0, _react.startTransition)(()=>{ (0, _useactionqueue.dispatchAppRouterAction)({ type: _routerreducertypes.ACTION_RESTORE, url: new URL(url != null ? url : href, href), tree }); }); }; /** * 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) { // Avoid a loop when Next.js internals trigger pushState/replaceState if ((data == null ? void 0 : data.__NA) || (data == null ? void 0 : 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) { // Avoid a loop when Next.js internals trigger pushState/replaceState if ((data == null ? void 0 : data.__NA) || (data == null ? void 0 : 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 } = 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 ]); const layoutRouterContext = (0, _react.useMemo)(()=>{ return { parentTree: tree, parentCacheNode: cache, parentSegmentPath: null, // Root node always has `url` // Provided in AppTreeContext to ensure it can be overwritten in layout-router url: canonicalUrl }; }, [ tree, cache, canonicalUrl ]); const globalLayoutRouterContext = (0, _react.useMemo)(()=>{ return { tree, focusAndScrollRef, nextUrl }; }, [ tree, focusAndScrollRef, nextUrl ]); 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] = matchingHead; head = /*#__PURE__*/ (0, _jsxruntime.jsx)(Head, { headCacheNode: headCacheNode }, headKey); } else { head = null; } let content = /*#__PURE__*/ (0, _jsxruntime.jsxs)(_redirectboundary.RedirectBoundary, { children: [ head, 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('./react-dev-overlay/app/hot-reloader-client').default; content = /*#__PURE__*/ (0, _jsxruntime.jsx)(HotReloader, { assetPrefix: assetPrefix, globalError: globalError, children: content }); } else { // In production, we only apply the user-customized global error boundary. content = /*#__PURE__*/ (0, _jsxruntime.jsx)(_errorboundary.ErrorBoundary, { 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.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(param) { let { actionQueue, globalErrorComponentAndStyles: [globalErrorComponent, globalErrorStyles], assetPrefix } = param; (0, _navfailurehandler.useNavFailureHandler)(); return /*#__PURE__*/ (0, _jsxruntime.jsx)(_errorboundary.ErrorBoundary, { // 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. errorComponent: _errorboundary.default, children: /*#__PURE__*/ (0, _jsxruntime.jsx)(Router, { actionQueue: actionQueue, assetPrefix: assetPrefix, globalError: [ globalErrorComponent, globalErrorStyles ] }) }); } 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 = process.env.NEXT_DEPLOYMENT_ID ? "?dpl=" + process.env.NEXT_DEPLOYMENT_ID : ''; 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