next
Version:
The React Framework
536 lines (534 loc) • 24.4 kB
JavaScript
'use client';
;
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