UNPKG

next

Version:

The React Framework

1,062 lines (1,061 loc) • 68 kB
import { mkdir, writeFile } from 'fs/promises'; import * as inspector from 'inspector'; import { join, extname, relative } from 'path'; import { pathToFileURL } from 'url'; import ws from 'next/dist/compiled/ws'; import { store as consoleStore } from '../../build/output/store'; import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types'; import { createDefineEnv, getBindingsSync, HmrTarget } from '../../build/swc'; import * as Log from '../../build/output/log'; import { BLOCKED_PAGES } from '../../shared/lib/constants'; import { getOverlayMiddleware, getSourceMapMiddleware, getOriginalStackFrames } from './middleware-turbopack'; import { PageNotFoundError } from '../../shared/lib/utils'; import { debounce } from '../utils'; import { deleteCache } from './require-cache'; import { clearAllModuleContexts, clearModuleContext } from '../lib/render-server'; import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page-path'; import { trace } from '../../trace'; import { AssetMapper, handleEntrypoints, handlePagesErrorRoute, handleRouteType, hasEntrypointForKey, msToNs, processTopLevelIssues, printNonFatalIssue, normalizedPageToTurbopackStructureRoute } from './turbopack-utils'; import { propagateServerField } from '../lib/router-utils/setup-dev-bundler'; import { TurbopackManifestLoader } from '../../shared/lib/turbopack/manifest-loader'; import { findPagePathData } from './on-demand-entry-handler'; import { getEntryKey, splitEntryKey } from '../../shared/lib/turbopack/entry-key'; import { createBinaryHmrMessageData, FAST_REFRESH_RUNTIME_RELOAD } from './messages'; import { generateEncryptionKeyBase64 } from '../app-render/encryption-utils-server'; import { isAppPageRouteDefinition } from '../route-definitions/app-page-route-definition'; import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'; import { isDeferredEntry } from '../../build/entries'; import { isMetadataRouteFile } from '../../lib/metadata/is-metadata-route'; import { setBundlerFindSourceMapImplementation } from '../patch-error-inspect'; import { getNextErrorFeedbackMiddleware } from '../../next-devtools/server/get-next-error-feedback-middleware'; import { formatIssue, isFileSystemCacheEnabledForDev, isWellKnownError, processIssues, renderStyledStringToErrorAnsi } from '../../shared/lib/turbopack/utils'; import { getDevOverlayFontMiddleware } from '../../next-devtools/server/font/get-dev-overlay-font-middleware'; import { devIndicatorServerState } from './dev-indicator-server-state'; import { getDisableDevIndicatorMiddleware } from '../../next-devtools/server/dev-indicator-middleware'; import { getRestartDevServerMiddleware } from '../../next-devtools/server/restart-dev-server-middleware'; import { backgroundLogCompilationEvents } from '../../shared/lib/turbopack/compilation-events'; import { getSupportedBrowsers } from '../../build/get-supported-browsers'; import { printBuildErrors } from '../../build/print-build-errors'; import { receiveBrowserLogsTurbopack, handleClientFileLogs } from './browser-logs/receive-logs'; import { normalizePath } from '../../lib/normalize-path'; import { devToolsConfigMiddleware, getDevToolsConfig } from '../../next-devtools/server/devtools-config-middleware'; import { getAttachNodejsDebuggerMiddleware } from '../../next-devtools/server/attach-nodejs-debugger-middleware'; import { connectReactDebugChannel, connectReactDebugChannelForHtmlRequest, deleteReactDebugChannelForHtmlRequest, setReactDebugChannelForHtmlRequest } from './debug-channel'; import { getVersionInfo, matchNextPageBundleRequest } from './hot-reloader-shared-utils'; import { getMcpMiddleware } from '../mcp/get-mcp-middleware'; import { handleErrorStateResponse } from '../mcp/tools/get-errors'; import { handlePageMetadataResponse } from '../mcp/tools/get-page-metadata'; import { setStackFrameResolver } from '../mcp/tools/utils/format-errors'; import { recordMcpTelemetry } from '../mcp/mcp-telemetry-tracker'; import { getFileLogger } from './browser-logs/file-logger'; import { sendSerializedErrorsToClient, sendSerializedErrorsToClientForHtmlRequest, setErrorsRscStreamForHtmlRequest } from './serialized-errors'; const wsServer = new ws.Server({ noServer: true }); const isTestMode = !!(process.env.NEXT_TEST_MODE || process.env.__NEXT_TEST_MODE || process.env.DEBUG); const sessionId = Math.floor(Number.MAX_SAFE_INTEGER * Math.random()); function setupServerHmr(project, { clear }) { const serverHmrSubscriptions = new Map(); /** * Subscribe to HMR updates for a server chunk. * @param chunkPath - Server chunk output path (e.g., "server/chunks/ssr/..._.js") */ function subscribeToServerHmr(chunkPath) { if (serverHmrSubscriptions.has(chunkPath)) { return; } const subscription = project.hmrEvents(chunkPath, HmrTarget.Server); serverHmrSubscriptions.set(chunkPath, subscription); (async ()=>{ // Skip initial state await subscription.next(); for await (const result of subscription){ const update = result; // Fully re-evaluate all chunks from disk. Clears the module cache and // notifies browsers to refetch RSC. if (update.type === 'restart') { await clear(); continue; } if (update.type !== 'partial') { continue; } const instruction = update.instruction; if (!instruction || instruction.type !== 'EcmascriptMergedUpdate') { continue; } if (typeof __turbopack_server_hmr_apply__ === 'function') { const applied = __turbopack_server_hmr_apply__(update); if (!applied) { await clear(); } } } })().catch(async (err)=>{ console.error('[Server HMR] Subscription error:', err); serverHmrSubscriptions.delete(chunkPath); await clear(); }); } // Listen to the Rust bindings update us on changing server HMR chunk paths ; (async ()=>{ try { const serverHmrChunkPaths = project.hmrChunkNamesSubscribe(HmrTarget.Server); // Process chunk paths (both initial and subsequent updates) for await (const data of serverHmrChunkPaths){ const currentChunkPaths = new Set(data.chunkNames.filter((path)=>path.endsWith('.js'))); // Clean up subscriptions for removed chunk paths (like when pages are deleted) const chunkPathsToRemove = []; for (const chunkPath of serverHmrSubscriptions.keys()){ if (!currentChunkPaths.has(chunkPath)) { chunkPathsToRemove.push(chunkPath); } } for (const chunkPath of chunkPathsToRemove){ var _subscription_return; const subscription = serverHmrSubscriptions.get(chunkPath); subscription == null ? void 0 : (_subscription_return = subscription.return) == null ? void 0 : _subscription_return.call(subscription); serverHmrSubscriptions.delete(chunkPath); } // Subscribe to HMR events for new server chunks for (const chunkPath of currentChunkPaths){ if (!serverHmrSubscriptions.has(chunkPath)) { subscribeToServerHmr(chunkPath); } } } } catch (err) { console.error('[Server HMR Setup] Error in chunk path subscription:', err); } })(); return serverHmrSubscriptions; } /** * Replaces turbopack:///[project] with the specified project in the `source` field. */ function rewriteTurbopackSources(projectRoot, sourceMap) { if ('sections' in sourceMap) { for (const section of sourceMap.sections){ rewriteTurbopackSources(projectRoot, section.map); } } else { for(let i = 0; i < sourceMap.sources.length; i++){ sourceMap.sources[i] = pathToFileURL(join(projectRoot, sourceMap.sources[i].replace(/turbopack:\/\/\/\[project\]/, ''))).toString(); } } } function getSourceMapFromTurbopack(project, projectRoot, sourceURL) { let sourceMapJson = null; try { sourceMapJson = project.getSourceMapSync(sourceURL); } catch (err) {} if (sourceMapJson === null) { return undefined; } else { const payload = JSON.parse(sourceMapJson); // The sourcemap from Turbopack is not yet written to disk so its `sources` // are not absolute paths yet. We need to rewrite them to be absolute paths. rewriteTurbopackSources(projectRoot, payload); return payload; } } export async function createHotReloaderTurbopack(opts, serverFields, distDir, resetFetch, lockfile, serverFastRefresh) { var _opts_nextConfig_turbopack, _nextConfig_watchOptions, _opts_nextConfig_experimental; const dev = true; const buildId = 'development'; const { nextConfig, dir: projectPath } = opts; const bindings = getBindingsSync(); // Turbopack requires native bindings and cannot run with WASM bindings. // Detect this early and give a clear, actionable error message. if (bindings.isWasm) { throw Object.defineProperty(new Error(`Turbopack is not supported on this platform (${process.platform}/${process.arch}) because native bindings are not available. ` + `Only WebAssembly (WASM) bindings were loaded, and Turbopack requires native bindings.\n\n` + `To use Next.js on this platform, use Webpack instead:\n` + ` next dev --webpack\n\n` + `For more information, see: https://nextjs.org/docs/app/api-reference/turbopack#supported-platforms`), "__NEXT_ERROR_CODE", { value: "E1049", enumerable: false, configurable: true }); } // For the debugging purpose, check if createNext or equivalent next instance setup in test cases // works correctly. Normally `run-test` hides output so only will be visible when `--debug` flag is used. if (isTestMode) { ; require('console').log('Creating turbopack project', { dir: projectPath, testMode: isTestMode }); } const hasRewrites = opts.fsChecker.rewrites.afterFiles.length > 0 || opts.fsChecker.rewrites.beforeFiles.length > 0 || opts.fsChecker.rewrites.fallback.length > 0; const hotReloaderSpan = trace('hot-reloader', undefined, { version: "16.2.1" }); // Ensure the hotReloaderSpan is flushed immediately as it's the parentSpan for all processing // of the current `next dev` invocation. hotReloaderSpan.stop(); // Initialize log monitor for file logging // Enable logging by default in development mode const mcpServerEnabled = !!nextConfig.experimental.mcpServer; const fileLogger = getFileLogger(); fileLogger.initialize(distDir, mcpServerEnabled); const encryptionKey = await generateEncryptionKeyBase64({ isBuild: false, distDir }); // TODO: Implement let clientRouterFilters; if (nextConfig.experimental.clientRouterFilter) { // TODO this need to be set correctly for filesystem cache to work } const supportedBrowsers = getSupportedBrowsers(projectPath, dev); const currentNodeJsVersion = process.versions.node; const rootPath = ((_opts_nextConfig_turbopack = opts.nextConfig.turbopack) == null ? void 0 : _opts_nextConfig_turbopack.root) || opts.nextConfig.outputFileTracingRoot || projectPath; const project = await bindings.turbo.createProject({ rootPath, projectPath: normalizePath(relative(rootPath, projectPath) || '.'), distDir, nextConfig: opts.nextConfig, watch: { enable: dev, pollIntervalMs: (_nextConfig_watchOptions = nextConfig.watchOptions) == null ? void 0 : _nextConfig_watchOptions.pollIntervalMs }, dev, env: process.env, defineEnv: createDefineEnv({ isTurbopack: true, clientRouterFilters, config: nextConfig, dev, distDir, projectPath, fetchCacheKeyPrefix: opts.nextConfig.experimental.fetchCacheKeyPrefix, hasRewrites, // TODO: Implement middlewareMatchers: undefined, rewrites: opts.fsChecker.rewrites }), buildId, encryptionKey, previewProps: opts.fsChecker.previewProps, browserslistQuery: supportedBrowsers.join(', '), noMangling: false, writeRoutesHashesManifest: false, currentNodeJsVersion, isPersistentCachingEnabled: isFileSystemCacheEnabledForDev(opts.nextConfig), nextVersion: "16.2.1", serverHmr: serverFastRefresh }, { memoryLimit: (_opts_nextConfig_experimental = opts.nextConfig.experimental) == null ? void 0 : _opts_nextConfig_experimental.turbopackMemoryLimit, isShortSession: false }); backgroundLogCompilationEvents(project, { eventTypes: [ 'StartupCacheInvalidationEvent', 'TimingEvent', 'SlowFilesystemEvent', 'TraceEvent' ], parentSpan: hotReloaderSpan }); setBundlerFindSourceMapImplementation(getSourceMapFromTurbopack.bind(null, project, projectPath)); // Set up code frame renderer using native bindings const { installCodeFrameSupport } = require('../lib/install-code-frame'); installCodeFrameSupport(); opts.onDevServerCleanup == null ? void 0 : opts.onDevServerCleanup.call(opts, async ()=>{ setBundlerFindSourceMapImplementation(()=>undefined); await project.onExit(); await (lockfile == null ? void 0 : lockfile.unlock()); }); const entrypointsSubscription = project.entrypointsSubscribe(); const currentWrittenEntrypoints = new Map(); const currentEntrypoints = { global: { app: undefined, document: undefined, error: undefined, middleware: undefined, instrumentation: undefined }, page: new Map(), app: new Map() }; const currentTopLevelIssues = new Map(); const currentEntryIssues = new Map(); const manifestLoader = new TurbopackManifestLoader({ buildId, distDir, encryptionKey, dev: true, sriEnabled: false }); // Dev specific const changeSubscriptions = new Map(); const serverPathState = new Map(); const readyIds = new Set(); let currentEntriesHandlingResolve; let currentEntriesHandling = new Promise((resolve)=>currentEntriesHandlingResolve = resolve); const assetMapper = new AssetMapper(); // Deferred entries state management const deferredEntriesConfig = nextConfig.experimental.deferredEntries; const hasDeferredEntriesConfig = deferredEntriesConfig && deferredEntriesConfig.length > 0; let onBeforeDeferredEntriesCalled = false; let onBeforeDeferredEntriesPromise = null; // Track non-deferred entries that are currently being built const nonDeferredBuildingEntries = new Set(); // Function to wait for all non-deferred entries to be built async function waitForNonDeferredEntries() { return new Promise((resolve)=>{ const checkEntries = ()=>{ // Check if there are any non-deferred entries that are still building if (nonDeferredBuildingEntries.size === 0) { resolve(); } else { // Check again after a short delay setTimeout(checkEntries, 100); } }; checkEntries(); }); } // Function to handle deferred entry processing async function processDeferredEntry() { if (!hasDeferredEntriesConfig) return; // Wait for all non-deferred entries to be built await waitForNonDeferredEntries(); // Call the onBeforeDeferredEntries callback once if (!onBeforeDeferredEntriesCalled) { onBeforeDeferredEntriesCalled = true; if (nextConfig.experimental.onBeforeDeferredEntries) { if (!onBeforeDeferredEntriesPromise) { onBeforeDeferredEntriesPromise = nextConfig.experimental.onBeforeDeferredEntries(); } await onBeforeDeferredEntriesPromise; } } else if (onBeforeDeferredEntriesPromise) { // Wait for any in-progress callback await onBeforeDeferredEntriesPromise; } } // Track whether HMR is pending - used to call callback once after HMR settles let hmrPendingDeferredCallback = false; // Debounced function to call onBeforeDeferredEntries after HMR // This prevents rapid-fire calls when turbopack fires many update events // Use 500ms debounce to ensure all rapid updates are batched together const callOnBeforeDeferredEntriesAfterHMR = debounce(()=>{ // Only call if HMR triggered a need for the callback if (hasDeferredEntriesConfig && hmrPendingDeferredCallback) { hmrPendingDeferredCallback = false; onBeforeDeferredEntriesCalled = true; if (nextConfig.experimental.onBeforeDeferredEntries) { onBeforeDeferredEntriesPromise = nextConfig.experimental.onBeforeDeferredEntries(); } } }, 500); function clearRequireCache(key, writtenEndpoint, { force } = {}) { var _currentEntrypoints_app_get; if (force) { for (const { path, contentHash } of writtenEndpoint.serverPaths){ // We ignore source maps if (path.endsWith('.map')) continue; const localKey = `${key}:${path}`; serverPathState.set(localKey, contentHash); serverPathState.set(path, contentHash); } } else { // Figure out if the server files have changed let hasChange = false; for (const { path, contentHash } of writtenEndpoint.serverPaths){ // We ignore source maps if (path.endsWith('.map')) continue; const localKey = `${key}:${path}`; const localHash = serverPathState.get(localKey); const globalHash = serverPathState.get(path); if (localHash && localHash !== contentHash || globalHash && globalHash !== contentHash) { hasChange = true; serverPathState.set(localKey, contentHash); serverPathState.set(path, contentHash); } else { if (!localHash) { serverPathState.set(localKey, contentHash); } if (!globalHash) { serverPathState.set(path, contentHash); } } } if (!hasChange) { return false; } } const serverPaths = writtenEndpoint.serverPaths.map(({ path: p })=>join(distDir, p)); const { type: entryType, page: entryPage } = splitEntryKey(key); const isAppPage = entryType === 'app' && ((_currentEntrypoints_app_get = currentEntrypoints.app.get(entryPage)) == null ? void 0 : _currentEntrypoints_app_get.type) === 'app-page'; // Server HMR only applies to app router pages since these use the Turbopack runtime. // Currently, this is only app router pages. // // This excludes: // - Pages Router pages // - Edge routes // - Middleware // - App Router route handlers (route.ts) const usesServerHmr = serverFastRefresh && isAppPage && writtenEndpoint.type !== 'edge'; const filesToDelete = []; for (const file of serverPaths){ clearModuleContext(file); const relativePath = relative(distDir, file); if (// For Pages Router, edge routes, middleware, and manifest files // (e.g., *_client-reference-manifest.js): clear the sharedCache in // evalManifest(), Node.js require.cache, and edge runtime module contexts. force || !usesServerHmr || !(serverHmrSubscriptions == null ? void 0 : serverHmrSubscriptions.has(relativePath))) { filesToDelete.push(file); } } deleteCache(filesToDelete); // Reset the fetch patch so patchFetch() can re-wrap on the next request. if (serverPaths.length > 0) { resetFetch(); } // Clear Turbopack's chunk-loading cache so chunks are re-required from disk on // the next request. // // For App Router with server HMR, this is normally skipped as server HMR // manages module updates in-place. However, it *is* required when force is `true` // (like for .env file or tsconfig changes). if ((!usesServerHmr || force) && typeof __next__clear_chunk_cache__ === 'function') { __next__clear_chunk_cache__(); } return true; } const buildingIds = new Set(); const startBuilding = (id, requestUrl, forceRebuild)=>{ if (!forceRebuild && readyIds.has(id)) { return ()=>{}; } if (buildingIds.size === 0) { consoleStore.setState({ loading: true, trigger: id, url: requestUrl }, true); } buildingIds.add(id); return function finishBuilding() { if (buildingIds.size === 0) { return; } readyIds.add(id); buildingIds.delete(id); if (buildingIds.size === 0) { hmrEventHappened = false; consoleStore.setState({ loading: false }, true); } }; }; let serverHmrSubscriptions; let hmrEventHappened = false; let hmrHash = 0; const clientsWithoutHtmlRequestId = new Set(); const clientsByHtmlRequestId = new Map(); const cacheStatusesByHtmlRequestId = new Map(); const clientStates = new WeakMap(); function sendToClient(client, message) { const data = typeof message.type === 'number' ? createBinaryHmrMessageData(message) : JSON.stringify(message); client.send(data); } function sendEnqueuedMessages() { for (const [, issueMap] of currentEntryIssues){ if ([ ...issueMap.values() ].filter((i)=>i.severity !== 'warning').length > 0) { // During compilation errors we want to delay the HMR events until errors are fixed return; } } for (const client of [ ...clientsWithoutHtmlRequestId, ...clientsByHtmlRequestId.values() ]){ const state = clientStates.get(client); if (!state) { continue; } for (const [, issueMap] of state.clientIssues){ if ([ ...issueMap.values() ].filter((i)=>i.severity !== 'warning').length > 0) { // During compilation errors we want to delay the HMR events until errors are fixed return; } } for (const message of state.messages.values()){ sendToClient(client, message); } state.messages.clear(); if (state.turbopackUpdates.length > 0) { sendToClient(client, { type: HMR_MESSAGE_SENT_TO_BROWSER.TURBOPACK_MESSAGE, data: state.turbopackUpdates }); state.turbopackUpdates.length = 0; } } } const sendEnqueuedMessagesDebounce = debounce(sendEnqueuedMessages, 2); const sendHmr = (id, message)=>{ for (const client of [ ...clientsWithoutHtmlRequestId, ...clientsByHtmlRequestId.values() ]){ var _clientStates_get; (_clientStates_get = clientStates.get(client)) == null ? void 0 : _clientStates_get.messages.set(id, message); } hmrEventHappened = true; sendEnqueuedMessagesDebounce(); }; function sendTurbopackMessage(payload) { // TODO(PACK-2049): For some reason we end up emitting hundreds of issues messages on bigger apps, // a lot of which are duplicates. // They are currently not handled on the client at all, so might as well not send them for now. payload.diagnostics = []; payload.issues = []; for (const client of [ ...clientsWithoutHtmlRequestId, ...clientsByHtmlRequestId.values() ]){ var _clientStates_get; (_clientStates_get = clientStates.get(client)) == null ? void 0 : _clientStates_get.turbopackUpdates.push(payload); } hmrEventHappened = true; sendEnqueuedMessagesDebounce(); } async function subscribeToClientChanges(key, includeIssues, endpoint, createMessage, onError) { if (changeSubscriptions.has(key)) { return; } const { side } = splitEntryKey(key); const changedPromise = endpoint[`${side}Changed`](includeIssues); changeSubscriptions.set(key, changedPromise); try { const changed = await changedPromise; for await (const change of changed){ processIssues(currentEntryIssues, key, change, false, true); // TODO: Get an actual content hash from Turbopack. const message = await createMessage(change, String(++hmrHash)); if (message) { sendHmr(key, message); } } } catch (e) { changeSubscriptions.delete(key); const payload = await (onError == null ? void 0 : onError(e)); if (payload) { sendHmr(key, payload); } return; } changeSubscriptions.delete(key); } async function unsubscribeFromClientChanges(key) { const subscription = await changeSubscriptions.get(key); if (subscription) { await (subscription.return == null ? void 0 : subscription.return.call(subscription)); changeSubscriptions.delete(key); } currentEntryIssues.delete(key); } async function subscribeToClientHmrEvents(client, id) { const key = getEntryKey('assets', 'client', id); if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) { // maybe throw an error / force the client to reload? return; } const state = clientStates.get(client); if (!state || state.subscriptions.has(id)) { return; } const subscription = project.hmrEvents(id, HmrTarget.Client); state.subscriptions.set(id, subscription); // The subscription will always emit once, which is the initial // computation. This is not a change, so swallow it. try { await subscription.next(); for await (const data of subscription){ processIssues(state.clientIssues, key, data, false, true); if (data.type !== 'issues') { sendTurbopackMessage(data); } } } catch (e) { // The client might be using an HMR session from a previous server, tell them // to fully reload the page to resolve the issue. We can't use // `hotReloader.send` since that would force every connected client to // reload, only this client is out of date. const reloadMessage = { type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, data: `error in HMR event subscription for ${id}: ${e}` }; sendToClient(client, reloadMessage); client.close(); return; } } function unsubscribeFromClientHmrEvents(client, id) { const state = clientStates.get(client); if (!state) { return; } const subscription = state.subscriptions.get(id); subscription == null ? void 0 : subscription.return(); const key = getEntryKey('assets', 'client', id); state.clientIssues.delete(key); } async function handleEntrypointsSubscription() { for await (const entrypoints of entrypointsSubscription){ if (!currentEntriesHandlingResolve) { currentEntriesHandling = new Promise(// eslint-disable-next-line no-loop-func (resolve)=>currentEntriesHandlingResolve = resolve); } // Always process issues/diagnostics, even if there are no entrypoints yet processTopLevelIssues(currentTopLevelIssues, entrypoints); // Certain crtical issues prevent any entrypoints from being constructed so return early if (!('routes' in entrypoints)) { printBuildErrors(entrypoints, true); currentEntriesHandlingResolve(); currentEntriesHandlingResolve = undefined; continue; } const routes = entrypoints.routes; const existingRoutes = [ ...currentEntrypoints.app.keys(), ...currentEntrypoints.page.keys() ]; const newRoutes = [ ...routes.keys() ]; const addedRoutes = newRoutes.filter((route)=>!currentEntrypoints.app.has(route) && !currentEntrypoints.page.has(route)); const removedRoutes = existingRoutes.filter((route)=>!routes.has(route)); await handleEntrypoints({ entrypoints: entrypoints, currentEntrypoints, currentEntryIssues, manifestLoader, devRewrites: opts.fsChecker.rewrites, productionRewrites: undefined, logErrors: true, dev: { assetMapper, changeSubscriptions, clients: [ ...clientsWithoutHtmlRequestId, ...clientsByHtmlRequestId.values() ], clientStates, serverFields, hooks: { handleWrittenEndpoint: (id, result, forceDeleteCache)=>{ currentWrittenEntrypoints.set(id, result); return clearRequireCache(id, result, { force: forceDeleteCache }); }, propagateServerField: propagateServerField.bind(null, opts), sendHmr, startBuilding, subscribeToChanges: subscribeToClientChanges, unsubscribeFromChanges: unsubscribeFromClientChanges, unsubscribeFromHmrEvents: unsubscribeFromClientHmrEvents } } }); // Reload matchers when the files have been compiled await propagateServerField(opts, 'reloadMatchers', undefined); if (addedRoutes.length > 0 || removedRoutes.length > 0) { // When the list of routes changes a new manifest should be fetched for Pages Router. hotReloader.send({ type: HMR_MESSAGE_SENT_TO_BROWSER.DEV_PAGES_MANIFEST_UPDATE, data: [ { devPagesManifest: true } ] }); } for (const route of addedRoutes){ hotReloader.send({ type: HMR_MESSAGE_SENT_TO_BROWSER.ADDED_PAGE, data: [ route ] }); } for (const route of removedRoutes){ hotReloader.send({ type: HMR_MESSAGE_SENT_TO_BROWSER.REMOVED_PAGE, data: [ route ] }); } currentEntriesHandlingResolve(); currentEntriesHandlingResolve = undefined; } } await mkdir(join(distDir, 'server'), { recursive: true }); await mkdir(join(distDir, 'static', buildId), { recursive: true }); await writeFile(join(distDir, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2)); const middlewares = [ getOverlayMiddleware({ project, projectPath, isSrcDir: opts.isSrcDir }), getSourceMapMiddleware(project), getNextErrorFeedbackMiddleware(opts.telemetry), getDevOverlayFontMiddleware(), getDisableDevIndicatorMiddleware(), getRestartDevServerMiddleware({ telemetry: opts.telemetry, turbopackProject: project }), devToolsConfigMiddleware({ distDir, sendUpdateSignal: (data)=>{ hotReloader.send({ type: HMR_MESSAGE_SENT_TO_BROWSER.DEVTOOLS_CONFIG, data }); } }), getAttachNodejsDebuggerMiddleware(), ...nextConfig.experimental.mcpServer ? [ getMcpMiddleware({ projectPath, distDir, nextConfig, pagesDir: opts.pagesDir, appDir: opts.appDir, sendHmrMessage: (message)=>hotReloader.send(message), getActiveConnectionCount: ()=>clientsWithoutHtmlRequestId.size + clientsByHtmlRequestId.size, getDevServerUrl: ()=>process.env.__NEXT_PRIVATE_ORIGIN }) ] : [] ]; setStackFrameResolver(async (request)=>{ return getOriginalStackFrames({ project, projectPath, isServer: request.isServer, isEdgeServer: request.isEdgeServer, isAppDirectory: request.isAppDirectory, frames: request.frames }); }); let versionInfoCached; // This fetch, even though not awaited, is not kicked off eagerly because the first `fetch()` in // Node.js adds roughly 20ms main-thread blocking to load the SSL certificate cache // We don't want that blocking time to be in the hot path for the `ready in` logging. // Instead, the fetch is kicked off lazily when the first `getVersionInfoCached()` is called. const getVersionInfoCached = ()=>{ if (!versionInfoCached) { versionInfoCached = getVersionInfo(); } return versionInfoCached; }; let devtoolsFrontendUrl; const inspectorURLRaw = inspector.url(); if (inspectorURLRaw !== undefined) { const inspectorURL = new URL(inspectorURLRaw); let debugInfo; try { const debugInfoList = await fetch(`http://${inspectorURL.host}/json/list`).then((res)=>res.json()); debugInfo = debugInfoList[0]; } catch {} if (debugInfo) { devtoolsFrontendUrl = debugInfo.devtoolsFrontendUrl; } } const hotReloader = { turbopackProject: project, activeWebpackConfigs: undefined, serverStats: null, edgeServerStats: null, async run (req, res, _parsedUrl) { var _req_url; // intercept page chunks request and ensure them with turbopack if ((_req_url = req.url) == null ? void 0 : _req_url.startsWith('/_next/static/chunks/pages/')) { const params = matchNextPageBundleRequest(req.url); if (params) { const decodedPagePath = `/${params.path.map((param)=>decodeURIComponent(param)).join('/')}`; const denormalizedPagePath = denormalizePagePath(decodedPagePath); await hotReloader.ensurePage({ page: denormalizedPagePath, clientOnly: false, definition: undefined, url: req.url }).catch(console.error); } } for (const middleware of middlewares){ let calledNext = false; await middleware(req, res, ()=>{ calledNext = true; }); if (!calledNext) { return { finished: true }; } } // Request was not finished. return { finished: undefined }; }, // TODO: Figure out if socket type can match the NextJsHotReloaderInterface onHMR (req, socket, head, onUpgrade) { wsServer.handleUpgrade(req, socket, head, (client)=>{ const clientIssues = new Map(); const subscriptions = new Map(); const htmlRequestId = req.url ? new URL(req.url, 'http://n').searchParams.get('id') : null; // Clients with a request ID are inferred App Router clients. If Cache // Components is not enabled, we consider those legacy clients. Pages // Router clients are also considered legacy clients. TODO: Maybe mark // clients as App Router / Pages Router clients explicitly, instead of // inferring it from the presence of a request ID. if (htmlRequestId) { clientsByHtmlRequestId.set(htmlRequestId, client); const enableCacheComponents = nextConfig.cacheComponents; if (enableCacheComponents) { onUpgrade(client, { isLegacyClient: false }); const cacheStatus = cacheStatusesByHtmlRequestId.get(htmlRequestId); if (cacheStatus !== undefined) { sendToClient(client, { type: HMR_MESSAGE_SENT_TO_BROWSER.CACHE_INDICATOR, state: cacheStatus }); cacheStatusesByHtmlRequestId.delete(htmlRequestId); } } else { onUpgrade(client, { isLegacyClient: true }); } connectReactDebugChannelForHtmlRequest(htmlRequestId, sendToClient.bind(null, client)); sendSerializedErrorsToClientForHtmlRequest(htmlRequestId, sendToClient.bind(null, client)); } else { clientsWithoutHtmlRequestId.add(client); onUpgrade(client, { isLegacyClient: true }); } clientStates.set(client, { clientIssues, messages: new Map(), turbopackUpdates: [], subscriptions }); client.on('close', ()=>{ // Remove active subscriptions for (const subscription of subscriptions.values()){ subscription.return == null ? void 0 : subscription.return.call(subscription); } clientStates.delete(client); if (htmlRequestId) { clientsByHtmlRequestId.delete(htmlRequestId); deleteReactDebugChannelForHtmlRequest(htmlRequestId); } else { clientsWithoutHtmlRequestId.delete(client); } }); client.addEventListener('message', async ({ data })=>{ const parsedData = JSON.parse(typeof data !== 'string' ? data.toString() : data); // Next.js messages switch(parsedData.event){ case 'span-end': { hotReloaderSpan.manualTraceChild(parsedData.spanName, msToNs(parsedData.startTime), msToNs(parsedData.endTime), parsedData.attributes); break; } case 'client-hmr-latency': hotReloaderSpan.manualTraceChild(parsedData.event, msToNs(parsedData.startTime), msToNs(parsedData.endTime), { updatedModules: parsedData.updatedModules, page: parsedData.page, isPageHidden: parsedData.isPageHidden }); break; case 'client-error': case 'client-warning': case 'client-success': case 'server-component-reload-page': case 'client-reload-page': case 'client-removed-page': case 'client-full-reload': const { hadRuntimeError, dependencyChain } = parsedData; if (hadRuntimeError) { Log.warn(FAST_REFRESH_RUNTIME_RELOAD); } if (Array.isArray(dependencyChain) && typeof dependencyChain[0] === 'string') { const cleanedModulePath = dependencyChain[0].replace(/^\[project\]/, '.').replace(/ \[.*\] \(.*\)$/, ''); Log.warn(`Fast Refresh had to perform a full reload when ${cleanedModulePath} changed. Read more: https://nextjs.org/docs/messages/fast-refresh-reload`); } break; case 'client-added-page': break; case 'browser-logs': { const browserToTerminalConfig = nextConfig.logging && nextConfig.logging.browserToTerminal; if (browserToTerminalConfig) { await receiveBrowserLogsTurbopack({ entries: parsedData.entries, router: parsedData.router, sourceType: parsedData.sourceType, project, projectPath, distDir, config: browserToTerminalConfig }); } break; } case 'client-file-logs': { // Always log to file regardless of terminal flag await handleClientFileLogs(parsedData.logs); break; } case 'ping': { break; } case 'mcp-error-state-response': { handleErrorStateResponse(parsedData.requestId, parsedData.errorState, parsedData.url); break; } case 'mcp-page-metadata-response': { handlePageMetadataResponse(parsedData.requestId, parsedData.segmentTrieData, parsedData.url); break; } default: // Might be a Turbopack message... if (!parsedData.type) { throw Object.defineProperty(new Error(`unrecognized HMR message "${data}"`), "__NEXT_ERROR_CODE", { value: "E155", enumerable: false, configurable: true }); } } // Turbopack messages switch(parsedData.type){ case 'turbopack-subscribe': subscribeToClientHmrEvents(client, parsedData.path); break; case 'turbopack-unsubscribe': unsubscribeFromClientHmrEvents(client, parsedData.path); break; default: if (!parsedData.event) { throw Object.defineProperty(new Error(`unrecognized Turbopack HMR message "${data}"`), "__NEXT_ERROR_CODE", { value: "E492", enumerable: false, configurable: true }); } } }); const turbopackConnectedMessage = { type: HMR_MESSAGE_SENT_TO_BROWSER.TURBOPACK_CONNECTED, data: { sessionId } }; sendToClient(client, turbopackConnectedMessage); const errors = []; for (const entryIssues of currentEntryIssues.values()){ for (const issue of entryIssues.values()){ if (issue.severity !== 'warning') { errors.push({ message: formatIssue(issue) }); } else { printNonFatalIssue(issue); } } } if (devIndicatorServerState.disabledUntil < Date.now()) { devIndicatorServerState.disabledUntil = 0; } ; (async function() { const versionInfo = await getVersionInfoCached(); const devToolsConfig = await getDevToolsConfig(distDir); const syncMessage = { type: HMR_MESSAGE_SENT_TO_BROWSER.SYNC, errors, warnings: [], hash: '', versionInfo, debug: { devtoolsFrontendUrl }, devIndicator: devIndicatorServerState, devToolsConfig }; sendToClient(client, syncMessage); })(); }); }, send (action) { const payload = JSON.stringify(action); for (const client of [ ...clientsWithoutHtmlRequestId, ...clientsByHtmlRequestId.values() ]){ client.send(payload); } }, sendToLegacyClients (action) { const payload = JSON.stringify(action); // Clients with a request ID are inferred App Router clients. If Cache // Components is not enabled, we consider those legacy clients. Pages // Router clients are also considered legacy clients. TODO: Maybe mark // clients as App Router / Pages Router clients explicitly, instead of // inferring it from the presence of a request ID. if (!nextConfig.cacheComponents) { for (const client of clientsByHtmlRequestId.values()){ client.send(payload); } } for (const client of clientsWithoutHtmlRequestId){ client.send(payload); } }, setCacheStatus (status, htmlRequestId) { // Legacy clients don't have Cache Components. const client = clientsByHtmlRequestId.get(htmlRequestId); if (client !== undefined) { sendToClient(client, { type: HMR_MESSAGE_SENT_TO_BROWSER.CACHE_INDICATOR, state: status }); } else { // If the client is not connected, store the status so that we can send it // when the client connects. cacheStatusesByHtmlRequestId.set(htmlRequestId, status); } }, setReactDebugChannel (debugChannel, htmlRequestId, requestId) { const client = clientsByHtmlRequestId.get(htmlRequestId); if (htmlRequestId === requestId) { // The debug channel is for the HTML request. if (client) { // If the client is connected, we can connect the debug channel for // the HTML request immediately.