rag-context-optimizer / frontend /node_modules /next /dist /esm /server /dev /on-demand-entry-handler.js
| import createDebug from 'next/dist/compiled/debug'; | |
| import { EventEmitter } from 'events'; | |
| import { findPageFile } from '../lib/find-page-file'; | |
| import { getStaticInfoIncludingLayouts, runDependingOnPageType } from '../../build/entries'; | |
| import { join, posix } from 'path'; | |
| import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep'; | |
| import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path'; | |
| import { ensureLeadingSlash } from '../../shared/lib/page-path/ensure-leading-slash'; | |
| import { removePagePathTail } from '../../shared/lib/page-path/remove-page-path-tail'; | |
| import { reportTrigger } from '../../build/output'; | |
| import getRouteFromEntrypoint from '../get-route-from-entrypoint'; | |
| import { isInstrumentationHookFile, isInstrumentationHookFilename, isMiddlewareFile, isMiddlewareFilename } from '../../build/utils'; | |
| import { PageNotFoundError, stringifyError } from '../../shared/lib/utils'; | |
| import { COMPILER_INDEXES, COMPILER_NAMES, RSC_MODULE_TYPES, UNDERSCORE_NOT_FOUND_ROUTE_ENTRY } from '../../shared/lib/constants'; | |
| import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment'; | |
| import { HMR_ACTIONS_SENT_TO_BROWSER } from './hot-reloader-types'; | |
| import { isAppPageRouteDefinition } from '../route-definitions/app-page-route-definition'; | |
| import { scheduleOnNextTick } from '../../lib/scheduler'; | |
| import { Batcher } from '../../lib/batcher'; | |
| import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'; | |
| import { PAGE_TYPES } from '../../lib/page-types'; | |
| import { getNextFlightSegmentPath } from '../../client/flight-data-helpers'; | |
| const debug = createDebug('next:on-demand-entry-handler'); | |
| /** | |
| * Returns object keys with type inferred from the object key | |
| */ const keys = Object.keys; | |
| const COMPILER_KEYS = keys(COMPILER_INDEXES); | |
| function treePathToEntrypoint(segmentPath, parentPath) { | |
| const [parallelRouteKey, segment] = segmentPath; | |
| // TODO-APP: modify this path to cover parallelRouteKey convention | |
| const path = (parentPath ? parentPath + '/' : '') + (parallelRouteKey !== 'children' && !segment.startsWith('@') ? `@${parallelRouteKey}/` : '') + (segment === '' ? 'page' : segment); | |
| // Last segment | |
| if (segmentPath.length === 2) { | |
| return path; | |
| } | |
| const childSegmentPath = getNextFlightSegmentPath(segmentPath); | |
| return treePathToEntrypoint(childSegmentPath, path); | |
| } | |
| function convertDynamicParamTypeToSyntax(dynamicParamTypeShort, param) { | |
| switch(dynamicParamTypeShort){ | |
| case 'c': | |
| case 'ci': | |
| return `[...${param}]`; | |
| case 'oc': | |
| return `[[...${param}]]`; | |
| case 'd': | |
| case 'di': | |
| return `[${param}]`; | |
| default: | |
| throw Object.defineProperty(new Error('Unknown dynamic param type'), "__NEXT_ERROR_CODE", { | |
| value: "E378", | |
| enumerable: false, | |
| configurable: true | |
| }); | |
| } | |
| } | |
| /** | |
| * format: {compiler type}@{page type}@{page path} | |
| * e.g. client@pages@/index | |
| * e.g. server@app@app/page | |
| * | |
| * This guarantees the uniqueness for each page, to avoid conflicts between app/ and pages/ | |
| */ export function getEntryKey(compilerType, pageBundleType, page) { | |
| // TODO: handle the /children slot better | |
| // this is a quick hack to handle when children is provided as children/page instead of /page | |
| const pageKey = page.replace(/(@[^/]+)\/children/g, '$1'); | |
| return `${compilerType}@${pageBundleType}@${pageKey}`; | |
| } | |
| function getPageBundleType(pageBundlePath) { | |
| // Handle special case for /_error | |
| if (pageBundlePath === '/_error') return PAGE_TYPES.PAGES; | |
| if (isMiddlewareFilename(pageBundlePath)) return PAGE_TYPES.ROOT; | |
| return pageBundlePath.startsWith('pages/') ? PAGE_TYPES.PAGES : pageBundlePath.startsWith('app/') ? PAGE_TYPES.APP : PAGE_TYPES.ROOT; | |
| } | |
| function getEntrypointsFromTree(tree, isFirst, parentPath = []) { | |
| const [segment, parallelRoutes] = tree; | |
| const currentSegment = Array.isArray(segment) ? convertDynamicParamTypeToSyntax(segment[2], segment[0]) : segment; | |
| const isPageSegment = currentSegment.startsWith(PAGE_SEGMENT_KEY); | |
| const currentPath = [ | |
| ...parentPath, | |
| isPageSegment ? '' : currentSegment | |
| ]; | |
| if (!isFirst && isPageSegment) { | |
| // TODO get rid of '' at the start of tree | |
| return [ | |
| treePathToEntrypoint(currentPath.slice(1)) | |
| ]; | |
| } | |
| return Object.keys(parallelRoutes).reduce((paths, key)=>{ | |
| const childTree = parallelRoutes[key]; | |
| const childPages = getEntrypointsFromTree(childTree, false, [ | |
| ...currentPath, | |
| key | |
| ]); | |
| return [ | |
| ...paths, | |
| ...childPages | |
| ]; | |
| }, []); | |
| } | |
| export const ADDED = Symbol('added'); | |
| export const BUILDING = Symbol('building'); | |
| export const BUILT = Symbol('built'); | |
| // Shadowing check in ESLint does not account for enum | |
| // eslint-disable-next-line no-shadow | |
| export var EntryTypes = /*#__PURE__*/ function(EntryTypes) { | |
| EntryTypes[EntryTypes["ENTRY"] = 0] = "ENTRY"; | |
| EntryTypes[EntryTypes["CHILD_ENTRY"] = 1] = "CHILD_ENTRY"; | |
| return EntryTypes; | |
| }({}); | |
| const entriesMap = new Map(); | |
| // remove /server from end of output for server compiler | |
| const normalizeOutputPath = (dir)=>dir.replace(/[/\\]server$/, ''); | |
| export const getEntries = (dir)=>{ | |
| dir = normalizeOutputPath(dir); | |
| const entries = entriesMap.get(dir) || {}; | |
| entriesMap.set(dir, entries); | |
| return entries; | |
| }; | |
| const invalidators = new Map(); | |
| export const getInvalidator = (dir)=>{ | |
| dir = normalizeOutputPath(dir); | |
| return invalidators.get(dir); | |
| }; | |
| const doneCallbacks = new EventEmitter(); | |
| const lastClientAccessPages = [ | |
| '' | |
| ]; | |
| const lastServerAccessPagesForAppDir = [ | |
| '' | |
| ]; | |
| // Make sure only one invalidation happens at a time | |
| // Otherwise, webpack hash gets changed and it'll force the client to reload. | |
| class Invalidator { | |
| constructor(multiCompiler){ | |
| this.building = new Set(); | |
| this.rebuildAgain = new Set(); | |
| this.multiCompiler = multiCompiler; | |
| } | |
| shouldRebuildAll() { | |
| return this.rebuildAgain.size > 0; | |
| } | |
| invalidate(compilerKeys = COMPILER_KEYS) { | |
| for (const key of compilerKeys){ | |
| var _this_multiCompiler_compilers_COMPILER_INDEXES_key_watching; | |
| // If there's a current build is processing, we won't abort it by invalidating. | |
| // (If aborted, it'll cause a client side hard reload) | |
| // But let it to invalidate just after the completion. | |
| // So, it can re-build the queued pages at once. | |
| if (this.building.has(key)) { | |
| this.rebuildAgain.add(key); | |
| continue; | |
| } | |
| this.building.add(key); | |
| (_this_multiCompiler_compilers_COMPILER_INDEXES_key_watching = this.multiCompiler.compilers[COMPILER_INDEXES[key]].watching) == null ? void 0 : _this_multiCompiler_compilers_COMPILER_INDEXES_key_watching.invalidate(); | |
| } | |
| } | |
| startBuilding(compilerKey) { | |
| this.building.add(compilerKey); | |
| } | |
| doneBuilding(compilerKeys = []) { | |
| const rebuild = []; | |
| for (const key of compilerKeys){ | |
| this.building.delete(key); | |
| if (this.rebuildAgain.has(key)) { | |
| rebuild.push(key); | |
| this.rebuildAgain.delete(key); | |
| } | |
| } | |
| if (rebuild.length > 0) { | |
| this.invalidate(rebuild); | |
| } | |
| } | |
| willRebuild(compilerKey) { | |
| return this.rebuildAgain.has(compilerKey); | |
| } | |
| } | |
| function disposeInactiveEntries(entries, maxInactiveAge) { | |
| Object.keys(entries).forEach((entryKey)=>{ | |
| const entryData = entries[entryKey]; | |
| const { lastActiveTime, status, dispose, bundlePath } = entryData; | |
| // TODO-APP: implement disposing of CHILD_ENTRY | |
| if (entryData.type === 1) { | |
| return; | |
| } | |
| // For the root middleware and the instrumentation hook files, | |
| // we don't dispose them periodically as it's needed for every request. | |
| if (isMiddlewareFilename(bundlePath) || isInstrumentationHookFilename(bundlePath)) { | |
| return; | |
| } | |
| if (dispose) // Skip pages already scheduled for disposing | |
| return; | |
| // This means this entry is currently building or just added | |
| // We don't need to dispose those entries. | |
| if (status !== BUILT) return; | |
| // We should not build the last accessed page even we didn't get any pings | |
| // Sometimes, it's possible our XHR ping to wait before completing other requests. | |
| // In that case, we should not dispose the current viewing page | |
| if (lastClientAccessPages.includes(entryKey) || lastServerAccessPagesForAppDir.includes(entryKey)) return; | |
| if (lastActiveTime && Date.now() - lastActiveTime > maxInactiveAge) { | |
| entries[entryKey].dispose = true; | |
| } | |
| }); | |
| } | |
| // Normalize both app paths and page paths | |
| function tryToNormalizePagePath(page) { | |
| try { | |
| return normalizePagePath(page); | |
| } catch (err) { | |
| console.error(err); | |
| throw new PageNotFoundError(page); | |
| } | |
| } | |
| /** | |
| * Attempts to find a page file path from the given pages absolute directory, | |
| * a page and allowed extensions. If the page can't be found it will throw an | |
| * error. It defaults the `/_error` page to Next.js internal error page. | |
| * | |
| * @param rootDir Absolute path to the project root. | |
| * @param page The page normalized (it will be denormalized). | |
| * @param extensions Array of page extensions. | |
| * @param pagesDir Absolute path to the pages folder with trailing `/pages`. | |
| * @param appDir Absolute path to the app folder with trailing `/app`. | |
| */ export async function findPagePathData(rootDir, page, extensions, pagesDir, appDir, isGlobalNotFoundEnabled) { | |
| const normalizedPagePath = tryToNormalizePagePath(page); | |
| let pagePath = null; | |
| const isInstrumentation = isInstrumentationHookFile(normalizedPagePath); | |
| if (isMiddlewareFile(normalizedPagePath) || isInstrumentation) { | |
| pagePath = await findPageFile(rootDir, normalizedPagePath, extensions, false); | |
| if (!pagePath) { | |
| throw new PageNotFoundError(normalizedPagePath); | |
| } | |
| const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), { | |
| extensions | |
| })); | |
| let bundlePath = normalizedPagePath; | |
| let pageKey = posix.normalize(pageUrl); | |
| if (isInstrumentation || isMiddlewareFile(normalizedPagePath)) { | |
| bundlePath = bundlePath.replace('/src', ''); | |
| pageKey = page.replace('/src', ''); | |
| } | |
| return { | |
| filename: join(rootDir, pagePath), | |
| bundlePath: bundlePath.slice(1), | |
| page: pageKey | |
| }; | |
| } | |
| // Check appDir first falling back to pagesDir | |
| if (appDir) { | |
| if (page === UNDERSCORE_NOT_FOUND_ROUTE_ENTRY) { | |
| // Load `global-not-found` when global-not-found is enabled. | |
| // Prefer to load it when both `global-not-found` and root `not-found` present. | |
| if (isGlobalNotFoundEnabled) { | |
| const globalNotFoundPath = await findPageFile(appDir, 'global-not-found', extensions, true); | |
| if (globalNotFoundPath) { | |
| return { | |
| filename: join(appDir, globalNotFoundPath), | |
| bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`, | |
| page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY | |
| }; | |
| } | |
| } else { | |
| // Then if global-not-found.js doesn't exist then load not-found.js | |
| const notFoundPath = await findPageFile(appDir, 'not-found', extensions, true); | |
| if (notFoundPath) { | |
| return { | |
| filename: join(appDir, notFoundPath), | |
| bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`, | |
| page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY | |
| }; | |
| } | |
| } | |
| // If they're not presented, then fallback to global-not-found | |
| return { | |
| filename: require.resolve('next/dist/client/components/builtin/global-not-found'), | |
| bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`, | |
| page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY | |
| }; | |
| } | |
| pagePath = await findPageFile(appDir, normalizedPagePath, extensions, true); | |
| if (pagePath) { | |
| const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), { | |
| keepIndex: true, | |
| extensions | |
| })); | |
| return { | |
| filename: join(appDir, pagePath), | |
| bundlePath: posix.join('app', pageUrl), | |
| page: posix.normalize(pageUrl) | |
| }; | |
| } | |
| } | |
| if (!pagePath && pagesDir) { | |
| pagePath = await findPageFile(pagesDir, normalizedPagePath, extensions, false); | |
| } | |
| if (pagePath !== null && pagesDir) { | |
| const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), { | |
| extensions | |
| })); | |
| return { | |
| filename: join(pagesDir, pagePath), | |
| bundlePath: posix.join('pages', normalizePagePath(pageUrl)), | |
| page: posix.normalize(pageUrl) | |
| }; | |
| } | |
| if (page === '/_error') { | |
| return { | |
| filename: require.resolve('next/dist/pages/_error'), | |
| bundlePath: page, | |
| page: normalizePathSep(page) | |
| }; | |
| } else { | |
| throw new PageNotFoundError(normalizedPagePath); | |
| } | |
| } | |
| export function onDemandEntryHandler({ hotReloader, maxInactiveAge, multiCompiler, nextConfig, pagesBufferLength, pagesDir, rootDir, appDir }) { | |
| const hasAppDir = !!appDir; | |
| let curInvalidator = getInvalidator(multiCompiler.outputPath); | |
| const curEntries = getEntries(multiCompiler.outputPath); | |
| if (!curInvalidator) { | |
| curInvalidator = new Invalidator(multiCompiler); | |
| invalidators.set(multiCompiler.outputPath, curInvalidator); | |
| } | |
| const startBuilding = (compilation)=>{ | |
| const compilationName = compilation.name; | |
| curInvalidator.startBuilding(compilationName); | |
| }; | |
| for (const compiler of multiCompiler.compilers){ | |
| compiler.hooks.make.tap('NextJsOnDemandEntries', startBuilding); | |
| } | |
| function getPagePathsFromEntrypoints(type, entrypoints) { | |
| const pagePaths = []; | |
| for (const entrypoint of entrypoints.values()){ | |
| const page = getRouteFromEntrypoint(entrypoint.name, hasAppDir); | |
| if (page) { | |
| var _entrypoint_name; | |
| const pageBundleType = ((_entrypoint_name = entrypoint.name) == null ? void 0 : _entrypoint_name.startsWith('app/')) ? PAGE_TYPES.APP : PAGE_TYPES.PAGES; | |
| pagePaths.push(getEntryKey(type, pageBundleType, page)); | |
| } else if (isMiddlewareFilename(entrypoint.name) || isInstrumentationHookFilename(entrypoint.name)) { | |
| pagePaths.push(getEntryKey(type, PAGE_TYPES.ROOT, `/${entrypoint.name}`)); | |
| } | |
| } | |
| return pagePaths; | |
| } | |
| for (const compiler of multiCompiler.compilers){ | |
| compiler.hooks.done.tap('NextJsOnDemandEntries', ()=>{ | |
| var _getInvalidator; | |
| return (_getInvalidator = getInvalidator(compiler.outputPath)) == null ? void 0 : _getInvalidator.doneBuilding([ | |
| compiler.name | |
| ]); | |
| }); | |
| } | |
| multiCompiler.hooks.done.tap('NextJsOnDemandEntries', (multiStats)=>{ | |
| var _getInvalidator; | |
| const [clientStats, serverStats, edgeServerStats] = multiStats.stats; | |
| const entryNames = [ | |
| ...getPagePathsFromEntrypoints(COMPILER_NAMES.client, clientStats.compilation.entrypoints), | |
| ...getPagePathsFromEntrypoints(COMPILER_NAMES.server, serverStats.compilation.entrypoints), | |
| ...edgeServerStats ? getPagePathsFromEntrypoints(COMPILER_NAMES.edgeServer, edgeServerStats.compilation.entrypoints) : [] | |
| ]; | |
| for (const name of entryNames){ | |
| const entry = curEntries[name]; | |
| if (!entry) { | |
| continue; | |
| } | |
| if (entry.status !== BUILDING) { | |
| continue; | |
| } | |
| entry.status = BUILT; | |
| doneCallbacks.emit(name); | |
| } | |
| (_getInvalidator = getInvalidator(multiCompiler.outputPath)) == null ? void 0 : _getInvalidator.doneBuilding([ | |
| ...COMPILER_KEYS | |
| ]); | |
| }); | |
| const pingIntervalTime = Math.max(1000, Math.min(5000, maxInactiveAge)); | |
| setInterval(function() { | |
| disposeInactiveEntries(curEntries, maxInactiveAge); | |
| }, pingIntervalTime + 1000).unref(); | |
| function handleAppDirPing(tree) { | |
| const pages = getEntrypointsFromTree(tree, true); | |
| for (const page of pages){ | |
| for (const compilerType of [ | |
| COMPILER_NAMES.client, | |
| COMPILER_NAMES.server, | |
| COMPILER_NAMES.edgeServer | |
| ]){ | |
| const entryKey = getEntryKey(compilerType, PAGE_TYPES.APP, `/${page}`); | |
| const entryInfo = curEntries[entryKey]; | |
| // If there's no entry, it may have been invalidated and needs to be re-built. | |
| if (!entryInfo) { | |
| continue; | |
| } | |
| // We don't need to maintain active state of anything other than BUILT entries | |
| if (entryInfo.status !== BUILT) continue; | |
| // If there's an entryInfo | |
| if (!lastServerAccessPagesForAppDir.includes(entryKey)) { | |
| lastServerAccessPagesForAppDir.unshift(entryKey); | |
| // Maintain the buffer max length | |
| // TODO: verify that the current pageKey is not at the end of the array as multiple entrypoints can exist | |
| if (lastServerAccessPagesForAppDir.length > pagesBufferLength) { | |
| lastServerAccessPagesForAppDir.pop(); | |
| } | |
| } | |
| entryInfo.lastActiveTime = Date.now(); | |
| entryInfo.dispose = false; | |
| } | |
| } | |
| } | |
| function handlePing(pg) { | |
| const page = normalizePathSep(pg); | |
| for (const compilerType of [ | |
| COMPILER_NAMES.client, | |
| COMPILER_NAMES.server, | |
| COMPILER_NAMES.edgeServer | |
| ]){ | |
| const entryKey = getEntryKey(compilerType, PAGE_TYPES.PAGES, page); | |
| const entryInfo = curEntries[entryKey]; | |
| // If there's no entry, it may have been invalidated and needs to be re-built. | |
| if (!entryInfo) { | |
| // if (page !== lastEntry) client pings, but there's no entry for page | |
| if (compilerType === COMPILER_NAMES.client) { | |
| return; | |
| } | |
| continue; | |
| } | |
| // We don't need to maintain active state of anything other than BUILT entries | |
| if (entryInfo.status !== BUILT) continue; | |
| // If there's an entryInfo | |
| if (!lastClientAccessPages.includes(entryKey)) { | |
| lastClientAccessPages.unshift(entryKey); | |
| // Maintain the buffer max length | |
| if (lastClientAccessPages.length > pagesBufferLength) { | |
| lastClientAccessPages.pop(); | |
| } | |
| } | |
| entryInfo.lastActiveTime = Date.now(); | |
| entryInfo.dispose = false; | |
| } | |
| return; | |
| } | |
| async function ensurePageImpl({ page, appPaths, definition, isApp, url }) { | |
| const stalledTime = 60; | |
| const stalledEnsureTimeout = setTimeout(()=>{ | |
| debug(`Ensuring ${page} has taken longer than ${stalledTime}s, if this continues to stall this may be a bug`); | |
| }, stalledTime * 1000); | |
| try { | |
| let route; | |
| if (definition) { | |
| route = definition; | |
| } else { | |
| route = await findPagePathData(rootDir, page, nextConfig.pageExtensions, pagesDir, appDir, !!nextConfig.experimental.globalNotFound); | |
| } | |
| const isInsideAppDir = !!appDir && route.filename.startsWith(appDir); | |
| if (typeof isApp === 'boolean' && isApp !== isInsideAppDir) { | |
| Error.stackTraceLimit = 15; | |
| throw Object.defineProperty(new Error(`Ensure bailed, found path "${route.page}" does not match ensure type (${isApp ? 'app' : 'pages'})`), "__NEXT_ERROR_CODE", { | |
| value: "E419", | |
| enumerable: false, | |
| configurable: true | |
| }); | |
| } | |
| const pageBundleType = getPageBundleType(route.bundlePath); | |
| const addEntry = (compilerType)=>{ | |
| const entryKey = getEntryKey(compilerType, pageBundleType, route.page); | |
| if (curEntries[entryKey] && // there can be an overlap in the entryKey for the instrumentation hook file and a page named the same | |
| // this is a quick fix to support this scenario by overwriting the instrumentation hook entry, since we only use it one time | |
| // any changes to the instrumentation hook file will require a restart of the dev server anyway | |
| !isInstrumentationHookFilename(curEntries[entryKey].bundlePath)) { | |
| curEntries[entryKey].dispose = false; | |
| curEntries[entryKey].lastActiveTime = Date.now(); | |
| if (curEntries[entryKey].status === BUILT) { | |
| return { | |
| entryKey, | |
| newEntry: false, | |
| shouldInvalidate: false | |
| }; | |
| } | |
| return { | |
| entryKey, | |
| newEntry: false, | |
| shouldInvalidate: true | |
| }; | |
| } | |
| curEntries[entryKey] = { | |
| type: 0, | |
| appPaths, | |
| absolutePagePath: route.filename, | |
| request: route.filename, | |
| bundlePath: route.bundlePath, | |
| dispose: false, | |
| lastActiveTime: Date.now(), | |
| status: ADDED | |
| }; | |
| return { | |
| entryKey: entryKey, | |
| newEntry: true, | |
| shouldInvalidate: true | |
| }; | |
| }; | |
| const staticInfo = await getStaticInfoIncludingLayouts({ | |
| page, | |
| pageFilePath: route.filename, | |
| isInsideAppDir, | |
| pageExtensions: nextConfig.pageExtensions, | |
| isDev: true, | |
| config: nextConfig, | |
| appDir | |
| }); | |
| const added = new Map(); | |
| const isServerComponent = isInsideAppDir && staticInfo.rsc !== RSC_MODULE_TYPES.client; | |
| let pageRuntime = staticInfo.runtime; | |
| runDependingOnPageType({ | |
| page: route.page, | |
| pageRuntime, | |
| pageType: pageBundleType, | |
| onClient: ()=>{ | |
| // Skip adding the client entry for app / Server Components. | |
| if (isServerComponent || isInsideAppDir) { | |
| return; | |
| } | |
| added.set(COMPILER_NAMES.client, addEntry(COMPILER_NAMES.client)); | |
| }, | |
| onServer: ()=>{ | |
| added.set(COMPILER_NAMES.server, addEntry(COMPILER_NAMES.server)); | |
| const edgeServerEntry = getEntryKey(COMPILER_NAMES.edgeServer, pageBundleType, route.page); | |
| if (curEntries[edgeServerEntry] && !isInstrumentationHookFile(route.page)) { | |
| // Runtime switched from edge to server | |
| delete curEntries[edgeServerEntry]; | |
| } | |
| }, | |
| onEdgeServer: ()=>{ | |
| added.set(COMPILER_NAMES.edgeServer, addEntry(COMPILER_NAMES.edgeServer)); | |
| const serverEntry = getEntryKey(COMPILER_NAMES.server, pageBundleType, route.page); | |
| if (curEntries[serverEntry] && !isInstrumentationHookFile(route.page)) { | |
| // Runtime switched from server to edge | |
| delete curEntries[serverEntry]; | |
| } | |
| } | |
| }); | |
| const addedValues = [ | |
| ...added.values() | |
| ]; | |
| const entriesThatShouldBeInvalidated = [ | |
| ...added.entries() | |
| ].filter(([, entry])=>entry.shouldInvalidate); | |
| const hasNewEntry = addedValues.some((entry)=>entry.newEntry); | |
| if (hasNewEntry) { | |
| const routePage = isApp ? route.page : normalizeAppPath(route.page); | |
| reportTrigger(routePage, url); | |
| } | |
| if (entriesThatShouldBeInvalidated.length > 0) { | |
| const invalidatePromise = Promise.all(entriesThatShouldBeInvalidated.map(([compilerKey, { entryKey }])=>{ | |
| return new Promise((resolve, reject)=>{ | |
| doneCallbacks.once(entryKey, (err)=>{ | |
| if (err) { | |
| return reject(err); | |
| } | |
| // If the invalidation also triggers a rebuild, we need to | |
| // wait for that additional build to prevent race conditions. | |
| const needsRebuild = curInvalidator.willRebuild(compilerKey); | |
| if (needsRebuild) { | |
| doneCallbacks.once(entryKey, (rebuildErr)=>{ | |
| if (rebuildErr) { | |
| return reject(rebuildErr); | |
| } | |
| resolve(); | |
| }); | |
| } else { | |
| resolve(); | |
| } | |
| }); | |
| }); | |
| })); | |
| curInvalidator.invalidate([ | |
| ...added.keys() | |
| ]); | |
| await invalidatePromise; | |
| } | |
| } finally{ | |
| clearTimeout(stalledEnsureTimeout); | |
| } | |
| } | |
| // Make sure that we won't have multiple invalidations ongoing concurrently. | |
| const batcher = Batcher.create({ | |
| // The cache key here is composed of the elements that affect the | |
| // compilation, namely, the page, whether it's client only, and whether | |
| // it's an app page. This ensures that we don't have multiple compilations | |
| // for the same page happening concurrently. | |
| // | |
| // We don't include the whole match because it contains match specific | |
| // parameters (like route params) that would just bust this cache. Any | |
| // details that would possibly bust the cache should be listed here. | |
| cacheKeyFn: (options)=>JSON.stringify(options), | |
| // Schedule the invocation of the ensurePageImpl function on the next tick. | |
| schedulerFn: scheduleOnNextTick | |
| }); | |
| return { | |
| async ensurePage ({ page, appPaths = null, definition, isApp, url }) { | |
| // If the route is actually an app page route, then we should have access | |
| // to the app route definition, and therefore, the appPaths from it. | |
| if (!appPaths && definition && isAppPageRouteDefinition(definition)) { | |
| appPaths = definition.appPaths; | |
| } | |
| // Wrap the invocation of the ensurePageImpl function in the pending | |
| // wrapper, which will ensure that we don't have multiple compilations | |
| // for the same page happening concurrently. | |
| return batcher.batch({ | |
| page, | |
| appPaths, | |
| definition, | |
| isApp | |
| }, async ()=>{ | |
| await ensurePageImpl({ | |
| page, | |
| appPaths, | |
| definition, | |
| isApp, | |
| url | |
| }); | |
| }); | |
| }, | |
| onHMR (client, getHmrServerError) { | |
| let bufferedHmrServerError = null; | |
| client.addEventListener('close', ()=>{ | |
| bufferedHmrServerError = null; | |
| }); | |
| client.addEventListener('message', ({ data })=>{ | |
| try { | |
| const error = getHmrServerError(); | |
| // New error occurred: buffered error is flushed and new error occurred | |
| if (!bufferedHmrServerError && error) { | |
| hotReloader.send({ | |
| action: HMR_ACTIONS_SENT_TO_BROWSER.SERVER_ERROR, | |
| errorJSON: stringifyError(error) | |
| }); | |
| bufferedHmrServerError = null; | |
| } | |
| const parsedData = JSON.parse(typeof data !== 'string' ? data.toString() : data); | |
| if (parsedData.event === 'ping') { | |
| if (parsedData.appDirRoute) { | |
| handleAppDirPing(parsedData.tree); | |
| } else { | |
| handlePing(parsedData.page); | |
| } | |
| } | |
| } catch {} | |
| }); | |
| } | |
| }; | |
| } | |
| //# sourceMappingURL=on-demand-entry-handler.js.map |