UNPKG

next

Version:

The React Framework

292 lines (290 loc) • 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { IDLE_LINK_STATUS: null, PENDING_LINK_STATUS: null, mountFormInstance: null, mountLinkInstance: null, onLinkVisibilityChanged: null, onNavigationIntent: null, pingVisibleLinks: null, setLinkForCurrentNavigation: null, unmountLinkForCurrentNavigation: null, unmountPrefetchableInstance: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { IDLE_LINK_STATUS: function() { return IDLE_LINK_STATUS; }, PENDING_LINK_STATUS: function() { return PENDING_LINK_STATUS; }, mountFormInstance: function() { return mountFormInstance; }, mountLinkInstance: function() { return mountLinkInstance; }, onLinkVisibilityChanged: function() { return onLinkVisibilityChanged; }, onNavigationIntent: function() { return onNavigationIntent; }, pingVisibleLinks: function() { return pingVisibleLinks; }, setLinkForCurrentNavigation: function() { return setLinkForCurrentNavigation; }, unmountLinkForCurrentNavigation: function() { return unmountLinkForCurrentNavigation; }, unmountPrefetchableInstance: function() { return unmountPrefetchableInstance; } }); const _types = require("./segment-cache/types"); const _cachekey = require("./segment-cache/cache-key"); const _scheduler = require("./segment-cache/scheduler"); const _react = require("react"); // Tracks the most recently navigated link instance. When null, indicates // the current navigation was not initiated by a link click. let linkForMostRecentNavigation = null; const PENDING_LINK_STATUS = { pending: true }; const IDLE_LINK_STATUS = { pending: false }; function setLinkForCurrentNavigation(link) { (0, _react.startTransition)(()=>{ linkForMostRecentNavigation?.setOptimisticLinkStatus(IDLE_LINK_STATUS); link?.setOptimisticLinkStatus(PENDING_LINK_STATUS); linkForMostRecentNavigation = link; }); } function unmountLinkForCurrentNavigation(link) { if (linkForMostRecentNavigation === link) { linkForMostRecentNavigation = null; } } // Use a WeakMap to associate a Link instance with its DOM element. This is // used by the IntersectionObserver to track the link's visibility. const prefetchable = typeof WeakMap === 'function' ? new WeakMap() : new Map(); // A Set of the currently visible links. We re-prefetch visible links after a // cache invalidation, or when the current URL changes. It's a separate data // structure from the WeakMap above because only the visible links need to // be enumerated. const prefetchableAndVisible = new Set(); // A single IntersectionObserver instance shared by all <Link> components. const observer = typeof IntersectionObserver === 'function' ? new IntersectionObserver(handleIntersect, { rootMargin: '200px' }) : null; function observeVisibility(element, instance) { const existingInstance = prefetchable.get(element); if (existingInstance !== undefined) { // This shouldn't happen because each <Link> component should have its own // anchor tag instance, but it's defensive coding to avoid a memory leak in // case there's a logical error somewhere else. unmountPrefetchableInstance(element); } // Only track prefetchable links that have a valid prefetch URL prefetchable.set(element, instance); if (observer !== null) { observer.observe(element); } } function coercePrefetchableUrl(href) { if (typeof window !== 'undefined') { const { createPrefetchURL } = require('./app-router-utils'); try { return createPrefetchURL(href); } catch { // createPrefetchURL sometimes throws an error if an invalid URL is // provided, though I'm not sure if it's actually necessary. // TODO: Consider removing the throw from the inner function, or change it // to reportError. Or maybe the error isn't even necessary for automatic // prefetches, just navigations. const reportErrorFn = typeof reportError === 'function' ? reportError : console.error; reportErrorFn(`Cannot prefetch '${href}' because it cannot be converted to a URL.`); return null; } } else { return null; } } function mountLinkInstance(element, href, router, fetchStrategy, prefetchEnabled, setOptimisticLinkStatus) { if (prefetchEnabled) { const prefetchURL = coercePrefetchableUrl(href); if (prefetchURL !== null) { const instance = { router, fetchStrategy, isVisible: false, prefetchTask: null, prefetchHref: prefetchURL.href, setOptimisticLinkStatus }; // We only observe the link's visibility if it's prefetchable. For // example, this excludes links to external URLs. observeVisibility(element, instance); return instance; } } // If the link is not prefetchable, we still create an instance so we can // track its optimistic state (i.e. useLinkStatus). const instance = { router, fetchStrategy, isVisible: false, prefetchTask: null, prefetchHref: null, setOptimisticLinkStatus }; return instance; } function mountFormInstance(element, href, router, fetchStrategy) { const prefetchURL = coercePrefetchableUrl(href); if (prefetchURL === null) { // This href is not prefetchable, so we don't track it. // TODO: We currently observe/unobserve a form every time its href changes. // For Links, this isn't a big deal because the href doesn't usually change, // but for forms it's extremely common. We should optimize this. return; } const instance = { router, fetchStrategy, isVisible: false, prefetchTask: null, prefetchHref: prefetchURL.href, setOptimisticLinkStatus: null }; observeVisibility(element, instance); } function unmountPrefetchableInstance(element) { const instance = prefetchable.get(element); if (instance !== undefined) { prefetchable.delete(element); prefetchableAndVisible.delete(instance); const prefetchTask = instance.prefetchTask; if (prefetchTask !== null) { (0, _scheduler.cancelPrefetchTask)(prefetchTask); } } if (observer !== null) { observer.unobserve(element); } } function handleIntersect(entries) { for (const entry of entries){ // Some extremely old browsers or polyfills don't reliably support // isIntersecting so we check intersectionRatio instead. (Do we care? Not // really. But whatever this is fine.) const isVisible = entry.intersectionRatio > 0; onLinkVisibilityChanged(entry.target, isVisible); } } function onLinkVisibilityChanged(element, isVisible) { if (process.env.NODE_ENV !== 'production') { // Prefetching on viewport is disabled in development for performance // reasons, because it requires compiling the target page. // TODO: Investigate re-enabling this. return; } const instance = prefetchable.get(element); if (instance === undefined) { return; } instance.isVisible = isVisible; if (isVisible) { prefetchableAndVisible.add(instance); } else { prefetchableAndVisible.delete(instance); } rescheduleLinkPrefetch(instance, _types.PrefetchPriority.Default); } function onNavigationIntent(element, unstable_upgradeToDynamicPrefetch) { const instance = prefetchable.get(element); if (instance === undefined) { return; } // Prefetch the link on hover/touchstart. if (instance !== undefined) { if (process.env.__NEXT_DYNAMIC_ON_HOVER && unstable_upgradeToDynamicPrefetch) { // Switch to a full prefetch instance.fetchStrategy = _types.FetchStrategy.Full; } rescheduleLinkPrefetch(instance, _types.PrefetchPriority.Intent); } } function rescheduleLinkPrefetch(instance, priority) { // Ensures that app-router-instance is not compiled in the server bundle if (typeof window !== 'undefined') { const existingPrefetchTask = instance.prefetchTask; if (!instance.isVisible) { // Cancel any in-progress prefetch task. (If it already finished then this // is a no-op.) if (existingPrefetchTask !== null) { (0, _scheduler.cancelPrefetchTask)(existingPrefetchTask); } // We don't need to reset the prefetchTask to null upon cancellation; an // old task object can be rescheduled with reschedulePrefetchTask. This is a // micro-optimization but also makes the code simpler (don't need to // worry about whether an old task object is stale). return; } const { getCurrentAppRouterState } = require('./app-router-instance'); const appRouterState = getCurrentAppRouterState(); if (appRouterState !== null) { const treeAtTimeOfPrefetch = appRouterState.tree; if (existingPrefetchTask === null) { // Initiate a prefetch task. const nextUrl = appRouterState.nextUrl; const cacheKey = (0, _cachekey.createCacheKey)(instance.prefetchHref, nextUrl); instance.prefetchTask = (0, _scheduler.schedulePrefetchTask)(cacheKey, treeAtTimeOfPrefetch, instance.fetchStrategy, priority, null); } else { // We already have an old task object that we can reschedule. This is // effectively the same as canceling the old task and creating a new one. (0, _scheduler.reschedulePrefetchTask)(existingPrefetchTask, treeAtTimeOfPrefetch, instance.fetchStrategy, priority); } } } } function pingVisibleLinks(nextUrl, tree) { // For each currently visible link, cancel the existing prefetch task (if it // exists) and schedule a new one. This is effectively the same as if all the // visible links left and then re-entered the viewport. // // This is called when the Next-Url or the base tree changes, since those // may affect the result of a prefetch task. It's also called after a // cache invalidation. for (const instance of prefetchableAndVisible){ const task = instance.prefetchTask; if (task !== null && !(0, _scheduler.isPrefetchTaskDirty)(task, nextUrl, tree)) { continue; } // Something changed. Cancel the existing prefetch task and schedule a // new one. if (task !== null) { (0, _scheduler.cancelPrefetchTask)(task); } const cacheKey = (0, _cachekey.createCacheKey)(instance.prefetchHref, nextUrl); instance.prefetchTask = (0, _scheduler.schedulePrefetchTask)(cacheKey, tree, instance.fetchStrategy, _types.PrefetchPriority.Default, null); } } 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=links.js.map