| | import type { |
| | ServerFields, |
| | SetupOpts, |
| | } from '../lib/router-utils/setup-dev-bundler' |
| | import type { |
| | Issue, |
| | TurbopackResult, |
| | Endpoint, |
| | RawEntrypoints, |
| | Update as TurbopackUpdate, |
| | WrittenEndpoint, |
| | } from '../../build/swc/types' |
| | import { |
| | type HmrMessageSentToBrowser, |
| | HMR_MESSAGE_SENT_TO_BROWSER, |
| | } from './hot-reloader-types' |
| | import * as Log from '../../build/output/log' |
| | import type { PropagateToWorkersField } from '../lib/router-utils/types' |
| | import type { TurbopackManifestLoader } from '../../shared/lib/turbopack/manifest-loader' |
| | import type { AppRoute, Entrypoints, PageRoute } from '../../build/swc/types' |
| | import { |
| | type EntryKey, |
| | getEntryKey, |
| | splitEntryKey, |
| | } from '../../shared/lib/turbopack/entry-key' |
| | import type ws from 'next/dist/compiled/ws' |
| | import { isMetadataRoute } from '../../lib/metadata/is-metadata-route' |
| | import type { CustomRoutes } from '../../lib/load-custom-routes' |
| | import { |
| | formatIssue, |
| | getIssueKey, |
| | isRelevantWarning, |
| | processIssues, |
| | renderStyledStringToErrorAnsi, |
| | type EntryIssuesMap, |
| | type TopLevelIssuesMap, |
| | } from '../../shared/lib/turbopack/utils' |
| | import { MIDDLEWARE_FILENAME, PROXY_FILENAME } from '../../lib/constants' |
| |
|
| | const onceErrorSet = new Set() |
| | |
| | |
| | |
| | |
| | |
| | |
| | function shouldEmitOnceWarning(issue: Issue): boolean { |
| | const { severity, title, stage } = issue |
| | if (severity === 'warning' && title.value === 'Invalid page configuration') { |
| | if (onceErrorSet.has(issue)) { |
| | return false |
| | } |
| | onceErrorSet.add(issue) |
| | } |
| | if ( |
| | severity === 'warning' && |
| | stage === 'config' && |
| | renderStyledStringToErrorAnsi(issue.title).includes("can't be external") |
| | ) { |
| | if (onceErrorSet.has(issue)) { |
| | return false |
| | } |
| | onceErrorSet.add(issue) |
| | } |
| |
|
| | return true |
| | } |
| |
|
| | |
| | |
| | export function printNonFatalIssue(issue: Issue) { |
| | if (isRelevantWarning(issue) && shouldEmitOnceWarning(issue)) { |
| | Log.warn(formatIssue(issue)) |
| | } |
| | } |
| |
|
| | export function processTopLevelIssues( |
| | currentTopLevelIssues: TopLevelIssuesMap, |
| | result: TurbopackResult |
| | ) { |
| | currentTopLevelIssues.clear() |
| |
|
| | for (const issue of result.issues) { |
| | const issueKey = getIssueKey(issue) |
| | currentTopLevelIssues.set(issueKey, issue) |
| | } |
| | } |
| |
|
| | const MILLISECONDS_IN_NANOSECOND = BigInt(1_000_000) |
| |
|
| | export function msToNs(ms: number): bigint { |
| | return BigInt(Math.floor(ms)) * MILLISECONDS_IN_NANOSECOND |
| | } |
| |
|
| | export type ChangeSubscriptions = Map< |
| | EntryKey, |
| | Promise<AsyncIterableIterator<TurbopackResult>> |
| | > |
| |
|
| | export type HandleWrittenEndpoint = ( |
| | key: EntryKey, |
| | result: TurbopackResult<WrittenEndpoint>, |
| | forceDeleteCache: boolean |
| | ) => boolean |
| |
|
| | export type StartChangeSubscription = ( |
| | key: EntryKey, |
| | includeIssues: boolean, |
| | endpoint: Endpoint, |
| | createMessage: ( |
| | change: TurbopackResult, |
| | hash: string |
| | ) => Promise<HmrMessageSentToBrowser> | HmrMessageSentToBrowser | void, |
| | onError?: ( |
| | e: Error |
| | ) => Promise<HmrMessageSentToBrowser> | HmrMessageSentToBrowser | void |
| | ) => Promise<void> |
| |
|
| | export type StopChangeSubscription = (key: EntryKey) => Promise<void> |
| |
|
| | export type SendHmr = (id: string, message: HmrMessageSentToBrowser) => void |
| |
|
| | export type StartBuilding = ( |
| | id: string, |
| | requestUrl: string | undefined, |
| | forceRebuild: boolean |
| | ) => () => void |
| |
|
| | export type ReadyIds = Set<string> |
| |
|
| | export type ClientState = { |
| | clientIssues: EntryIssuesMap |
| | messages: Map<string, HmrMessageSentToBrowser> |
| | turbopackUpdates: TurbopackUpdate[] |
| | subscriptions: Map<string, AsyncIterator<any>> |
| | } |
| |
|
| | export type ClientStateMap = WeakMap<ws, ClientState> |
| |
|
| | |
| | type HandleRouteTypeHooks = { |
| | handleWrittenEndpoint: HandleWrittenEndpoint |
| | subscribeToChanges: StartChangeSubscription |
| | } |
| |
|
| | export async function handleRouteType({ |
| | dev, |
| | page, |
| | pathname, |
| | route, |
| | currentEntryIssues, |
| | entrypoints, |
| | manifestLoader, |
| | readyIds, |
| | devRewrites, |
| | productionRewrites, |
| | hooks, |
| | logErrors, |
| | }: { |
| | dev: boolean |
| | page: string |
| | pathname: string |
| | route: PageRoute | AppRoute |
| | |
| | currentEntryIssues: EntryIssuesMap |
| | entrypoints: Entrypoints |
| | manifestLoader: TurbopackManifestLoader |
| | devRewrites: SetupOpts['fsChecker']['rewrites'] | undefined |
| | productionRewrites: CustomRoutes['rewrites'] | undefined |
| | logErrors: boolean |
| | |
| | readyIds?: ReadyIds // dev |
| | |
| | hooks?: HandleRouteTypeHooks // dev |
| | }) { |
| | const shouldCreateWebpackStats = process.env.TURBOPACK_STATS != null |
| |
|
| | switch (route.type) { |
| | case 'page': { |
| | const clientKey = getEntryKey('pages', 'client', page) |
| | const serverKey = getEntryKey('pages', 'server', page) |
| |
|
| | try { |
| | |
| | |
| | |
| | |
| | let documentOrAppChanged = false |
| | if (entrypoints.global.app) { |
| | const key = getEntryKey('pages', 'server', '_app') |
| |
|
| | const writtenEndpoint = await entrypoints.global.app.writeToDisk() |
| | documentOrAppChanged ||= |
| | hooks?.handleWrittenEndpoint(key, writtenEndpoint, false) ?? false |
| | processIssues( |
| | currentEntryIssues, |
| | key, |
| | writtenEndpoint, |
| | false, |
| | logErrors |
| | ) |
| | } |
| | await manifestLoader.loadBuildManifest('_app') |
| | await manifestLoader.loadPagesManifest('_app') |
| |
|
| | if (entrypoints.global.document) { |
| | const key = getEntryKey('pages', 'server', '_document') |
| |
|
| | const writtenEndpoint = |
| | await entrypoints.global.document.writeToDisk() |
| | documentOrAppChanged ||= |
| | hooks?.handleWrittenEndpoint(key, writtenEndpoint, false) ?? false |
| | processIssues( |
| | currentEntryIssues, |
| | key, |
| | writtenEndpoint, |
| | false, |
| | logErrors |
| | ) |
| | } |
| | await manifestLoader.loadPagesManifest('_document') |
| |
|
| | const writtenEndpoint = await route.htmlEndpoint.writeToDisk() |
| | hooks?.handleWrittenEndpoint( |
| | serverKey, |
| | writtenEndpoint, |
| | documentOrAppChanged |
| | ) |
| |
|
| | const type = writtenEndpoint?.type |
| |
|
| | await manifestLoader.loadClientBuildManifest(page) |
| | await manifestLoader.loadBuildManifest(page) |
| | await manifestLoader.loadPagesManifest(page) |
| | if (type === 'edge') { |
| | await manifestLoader.loadMiddlewareManifest(page, 'pages') |
| | } else { |
| | manifestLoader.deleteMiddlewareManifest(serverKey) |
| | } |
| | await manifestLoader.loadFontManifest('/_app', 'pages') |
| | await manifestLoader.loadFontManifest(page, 'pages') |
| |
|
| | if (shouldCreateWebpackStats) { |
| | await manifestLoader.loadWebpackStats(page, 'pages') |
| | } |
| |
|
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites, |
| | entrypoints, |
| | }) |
| |
|
| | processIssues( |
| | currentEntryIssues, |
| | serverKey, |
| | writtenEndpoint, |
| | false, |
| | logErrors |
| | ) |
| | } finally { |
| | if (dev) { |
| | |
| | |
| | hooks?.subscribeToChanges( |
| | serverKey, |
| | false, |
| | route.dataEndpoint, |
| | () => { |
| | |
| | readyIds?.delete(pathname) |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_ONLY_CHANGES, |
| | pages: [page], |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in ${page} data subscription: ${e}`, |
| | } |
| | } |
| | ) |
| | hooks?.subscribeToChanges( |
| | clientKey, |
| | false, |
| | route.htmlEndpoint, |
| | () => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES, |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in ${page} html subscription: ${e}`, |
| | } |
| | } |
| | ) |
| | if (entrypoints.global.document) { |
| | hooks?.subscribeToChanges( |
| | getEntryKey('pages', 'server', '_document'), |
| | false, |
| | entrypoints.global.document, |
| | () => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: '_document has changed (page route)', |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in _document subscription (page route): ${e}`, |
| | } |
| | } |
| | ) |
| | } |
| | } |
| | } |
| |
|
| | break |
| | } |
| | case 'page-api': { |
| | const key = getEntryKey('pages', 'server', page) |
| |
|
| | const writtenEndpoint = await route.endpoint.writeToDisk() |
| | hooks?.handleWrittenEndpoint(key, writtenEndpoint, false) |
| |
|
| | const type = writtenEndpoint.type |
| |
|
| | await manifestLoader.loadPagesManifest(page) |
| | if (type === 'edge') { |
| | await manifestLoader.loadMiddlewareManifest(page, 'pages') |
| | } else { |
| | manifestLoader.deleteMiddlewareManifest(key) |
| | } |
| |
|
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites, |
| | entrypoints, |
| | }) |
| |
|
| | processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors) |
| |
|
| | break |
| | } |
| | case 'app-page': { |
| | const key = getEntryKey('app', 'server', page) |
| |
|
| | const writtenEndpoint = await route.htmlEndpoint.writeToDisk() |
| | hooks?.handleWrittenEndpoint(key, writtenEndpoint, false) |
| |
|
| | if (dev) { |
| | |
| | |
| | hooks?.subscribeToChanges( |
| | key, |
| | true, |
| | route.rscEndpoint, |
| | (change, hash) => { |
| | if (change.issues.some((issue) => issue.severity === 'error')) { |
| | |
| | |
| | return |
| | } |
| | |
| | readyIds?.delete(pathname) |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES, |
| | hash, |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in ${page} app-page subscription: ${e}`, |
| | } |
| | } |
| | ) |
| | } |
| |
|
| | const type = writtenEndpoint.type |
| |
|
| | if (type === 'edge') { |
| | manifestLoader.loadMiddlewareManifest(page, 'app') |
| | } else { |
| | manifestLoader.deleteMiddlewareManifest(key) |
| | } |
| |
|
| | manifestLoader.loadBuildManifest(page, 'app') |
| | manifestLoader.loadAppPathsManifest(page) |
| | manifestLoader.loadActionManifest(page) |
| | manifestLoader.loadFontManifest(page, 'app') |
| |
|
| | if (shouldCreateWebpackStats) { |
| | manifestLoader.loadWebpackStats(page, 'app') |
| | } |
| |
|
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites, |
| | entrypoints, |
| | }) |
| |
|
| | processIssues(currentEntryIssues, key, writtenEndpoint, dev, logErrors) |
| |
|
| | break |
| | } |
| | case 'app-route': { |
| | const key = getEntryKey('app', 'server', page) |
| |
|
| | const writtenEndpoint = await route.endpoint.writeToDisk() |
| | hooks?.handleWrittenEndpoint(key, writtenEndpoint, false) |
| |
|
| | const type = writtenEndpoint.type |
| |
|
| | manifestLoader.loadAppPathsManifest(page) |
| |
|
| | if (type === 'edge') { |
| | manifestLoader.loadMiddlewareManifest(page, 'app') |
| | } else { |
| | manifestLoader.deleteMiddlewareManifest(key) |
| | } |
| |
|
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites, |
| | entrypoints, |
| | }) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors) |
| |
|
| | break |
| | } |
| | default: { |
| | throw new Error(`unknown route type ${(route as any).type} for ${page}`) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export class AssetMapper { |
| | private entryMap: Map<EntryKey, Set<string>> = new Map() |
| | private assetMap: Map<string, Set<EntryKey>> = new Map() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | setPathsForKey(key: EntryKey, assetPaths: string[]): void { |
| | this.delete(key) |
| |
|
| | const newAssetPaths = new Set(assetPaths) |
| | this.entryMap.set(key, newAssetPaths) |
| |
|
| | for (const assetPath of newAssetPaths) { |
| | let assetPathKeys = this.assetMap.get(assetPath) |
| | if (!assetPathKeys) { |
| | assetPathKeys = new Set() |
| | this.assetMap.set(assetPath, assetPathKeys) |
| | } |
| |
|
| | assetPathKeys!.add(key) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | delete(key: EntryKey) { |
| | for (const assetPath of this.getAssetPathsByKey(key)) { |
| | const assetPathKeys = this.assetMap.get(assetPath) |
| |
|
| | assetPathKeys?.delete(key) |
| |
|
| | if (!assetPathKeys?.size) { |
| | this.assetMap.delete(assetPath) |
| | } |
| | } |
| |
|
| | this.entryMap.delete(key) |
| | } |
| |
|
| | getAssetPathsByKey(key: EntryKey): string[] { |
| | return Array.from(this.entryMap.get(key) ?? []) |
| | } |
| |
|
| | getKeysByAsset(path: string): EntryKey[] { |
| | return Array.from(this.assetMap.get(path) ?? []) |
| | } |
| |
|
| | keys(): IterableIterator<EntryKey> { |
| | return this.entryMap.keys() |
| | } |
| | } |
| |
|
| | export function hasEntrypointForKey( |
| | entrypoints: Entrypoints, |
| | key: EntryKey, |
| | assetMapper: AssetMapper | undefined |
| | ): boolean { |
| | const { type, page } = splitEntryKey(key) |
| |
|
| | switch (type) { |
| | case 'app': |
| | return entrypoints.app.has(page) |
| | case 'pages': |
| | switch (page) { |
| | case '_app': |
| | return entrypoints.global.app != null |
| | case '_document': |
| | return entrypoints.global.document != null |
| | case '_error': |
| | return entrypoints.global.error != null |
| | default: |
| | return entrypoints.page.has(page) |
| | } |
| | case 'root': |
| | switch (page) { |
| | case 'middleware': |
| | return entrypoints.global.middleware != null |
| | case 'instrumentation': |
| | return entrypoints.global.instrumentation != null |
| | default: |
| | return false |
| | } |
| | case 'assets': |
| | if (!assetMapper) { |
| | return false |
| | } |
| |
|
| | return assetMapper |
| | .getKeysByAsset(page) |
| | .some((pageKey) => |
| | hasEntrypointForKey(entrypoints, pageKey, assetMapper) |
| | ) |
| | default: { |
| | |
| | const _: never = type |
| | return false |
| | } |
| | } |
| | } |
| |
|
| | |
| | type HandleEntrypointsHooks = { |
| | handleWrittenEndpoint: HandleWrittenEndpoint |
| | propagateServerField: ( |
| | field: PropagateToWorkersField, |
| | args: any |
| | ) => Promise<void> |
| | sendHmr: SendHmr |
| | startBuilding: StartBuilding |
| | subscribeToChanges: StartChangeSubscription |
| | unsubscribeFromChanges: StopChangeSubscription |
| | unsubscribeFromHmrEvents: (client: ws, id: string) => void |
| | } |
| |
|
| | type HandleEntrypointsDevOpts = { |
| | assetMapper: AssetMapper |
| | changeSubscriptions: ChangeSubscriptions |
| | clients: Array<ws> |
| | clientStates: ClientStateMap |
| | serverFields: ServerFields |
| |
|
| | hooks: HandleEntrypointsHooks |
| | } |
| |
|
| | export async function handleEntrypoints({ |
| | entrypoints, |
| | |
| | currentEntrypoints, |
| | |
| | currentEntryIssues, |
| | manifestLoader, |
| | devRewrites, |
| | logErrors, |
| | dev, |
| | }: { |
| | entrypoints: TurbopackResult<RawEntrypoints> |
| | |
| | currentEntrypoints: Entrypoints |
| | |
| | currentEntryIssues: EntryIssuesMap |
| | manifestLoader: TurbopackManifestLoader |
| | devRewrites: SetupOpts['fsChecker']['rewrites'] | undefined |
| | productionRewrites: CustomRoutes['rewrites'] | undefined |
| | logErrors: boolean |
| | |
| | dev: HandleEntrypointsDevOpts |
| | }) { |
| | currentEntrypoints.global.app = entrypoints.pagesAppEndpoint |
| | currentEntrypoints.global.document = entrypoints.pagesDocumentEndpoint |
| | currentEntrypoints.global.error = entrypoints.pagesErrorEndpoint |
| |
|
| | currentEntrypoints.global.instrumentation = entrypoints.instrumentation |
| |
|
| | currentEntrypoints.page.clear() |
| | currentEntrypoints.app.clear() |
| |
|
| | for (const [pathname, route] of entrypoints.routes) { |
| | switch (route.type) { |
| | case 'page': |
| | case 'page-api': |
| | currentEntrypoints.page.set(pathname, route) |
| | break |
| | case 'app-page': { |
| | route.pages.forEach((page) => { |
| | currentEntrypoints.app.set(page.originalName, { |
| | type: 'app-page', |
| | ...page, |
| | }) |
| | }) |
| | break |
| | } |
| | case 'app-route': { |
| | currentEntrypoints.app.set(route.originalName, route) |
| | break |
| | } |
| | case 'conflict': |
| | Log.info(`skipping ${pathname} (${route.type})`) |
| | break |
| | default: |
| | route satisfies never |
| | } |
| | } |
| |
|
| | if (dev) { |
| | await handleEntrypointsDevCleanup({ |
| | currentEntryIssues, |
| | currentEntrypoints, |
| |
|
| | ...dev, |
| | }) |
| | } |
| |
|
| | const { middleware, instrumentation } = entrypoints |
| |
|
| | |
| | |
| | |
| | if (currentEntrypoints.global.middleware && !middleware) { |
| | const key = getEntryKey('root', 'server', 'middleware') |
| | |
| | await dev?.hooks.unsubscribeFromChanges(key) |
| | currentEntryIssues.delete(key) |
| | dev.hooks.sendHmr('middleware', { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, |
| | }) |
| | } else if (!currentEntrypoints.global.middleware && middleware) { |
| | |
| | dev.hooks.sendHmr('middleware', { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, |
| | }) |
| | } |
| |
|
| | currentEntrypoints.global.middleware = middleware |
| |
|
| | if (instrumentation) { |
| | const processInstrumentation = async ( |
| | name: string, |
| | prop: 'nodeJs' | 'edge' |
| | ) => { |
| | const prettyName = { |
| | nodeJs: 'Node.js', |
| | edge: 'Edge', |
| | } |
| | const finishBuilding = dev.hooks.startBuilding( |
| | `instrumentation ${prettyName[prop]}`, |
| | undefined, |
| | true |
| | ) |
| | const key = getEntryKey('root', 'server', name) |
| |
|
| | const writtenEndpoint = await instrumentation[prop].writeToDisk() |
| | dev.hooks.handleWrittenEndpoint(key, writtenEndpoint, false) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) |
| | finishBuilding() |
| | } |
| | await processInstrumentation('instrumentation.nodeJs', 'nodeJs') |
| | await processInstrumentation('instrumentation.edge', 'edge') |
| | await manifestLoader.loadMiddlewareManifest( |
| | 'instrumentation', |
| | 'instrumentation' |
| | ) |
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites: undefined, |
| | entrypoints: currentEntrypoints, |
| | }) |
| |
|
| | dev.serverFields.actualInstrumentationHookFile = '/instrumentation' |
| | await dev.hooks.propagateServerField( |
| | 'actualInstrumentationHookFile', |
| | dev.serverFields.actualInstrumentationHookFile |
| | ) |
| | } else { |
| | dev.serverFields.actualInstrumentationHookFile = undefined |
| | await dev.hooks.propagateServerField( |
| | 'actualInstrumentationHookFile', |
| | dev.serverFields.actualInstrumentationHookFile |
| | ) |
| | } |
| |
|
| | if (middleware) { |
| | const key = getEntryKey('root', 'server', 'middleware') |
| |
|
| | const endpoint = middleware.endpoint |
| | const triggerName = middleware.isProxy |
| | ? PROXY_FILENAME |
| | : MIDDLEWARE_FILENAME |
| |
|
| | async function processMiddleware() { |
| | const finishBuilding = dev.hooks.startBuilding( |
| | triggerName, |
| | undefined, |
| | true |
| | ) |
| | const writtenEndpoint = await endpoint.writeToDisk() |
| | dev.hooks.handleWrittenEndpoint(key, writtenEndpoint, false) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) |
| | await manifestLoader.loadMiddlewareManifest('middleware', 'middleware') |
| | const middlewareConfig = |
| | manifestLoader.getMiddlewareManifest(key)?.middleware['/'] |
| |
|
| | if (dev && middlewareConfig) { |
| | dev.serverFields.middleware = { |
| | match: null as any, |
| | page: '/', |
| | matchers: middlewareConfig.matchers, |
| | } |
| | } |
| | finishBuilding() |
| | } |
| | await processMiddleware() |
| |
|
| | if (dev) { |
| | dev?.hooks.subscribeToChanges( |
| | key, |
| | false, |
| | endpoint, |
| | async () => { |
| | const finishBuilding = dev.hooks.startBuilding( |
| | triggerName, |
| | undefined, |
| | true |
| | ) |
| | await processMiddleware() |
| | await dev.hooks.propagateServerField( |
| | 'actualMiddlewareFile', |
| | dev.serverFields.actualMiddlewareFile |
| | ) |
| | await dev.hooks.propagateServerField( |
| | 'middleware', |
| | dev.serverFields.middleware |
| | ) |
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites: undefined, |
| | entrypoints: currentEntrypoints, |
| | }) |
| |
|
| | finishBuilding?.() |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, |
| | } |
| | }, |
| | () => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, |
| | } |
| | } |
| | ) |
| | } |
| | } else { |
| | manifestLoader.deleteMiddlewareManifest( |
| | getEntryKey('root', 'server', 'middleware') |
| | ) |
| | dev.serverFields.actualMiddlewareFile = undefined |
| | dev.serverFields.middleware = undefined |
| | } |
| |
|
| | await dev.hooks.propagateServerField( |
| | 'actualMiddlewareFile', |
| | dev.serverFields.actualMiddlewareFile |
| | ) |
| | await dev.hooks.propagateServerField( |
| | 'middleware', |
| | dev.serverFields.middleware |
| | ) |
| | } |
| |
|
| | async function handleEntrypointsDevCleanup({ |
| | currentEntryIssues, |
| | currentEntrypoints, |
| | |
| | assetMapper, |
| | changeSubscriptions, |
| | clients, |
| | clientStates, |
| | |
| | hooks, |
| | }: { |
| | currentEntrypoints: Entrypoints |
| | currentEntryIssues: EntryIssuesMap |
| | } & HandleEntrypointsDevOpts) { |
| | |
| | for (const key of assetMapper.keys()) { |
| | if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) { |
| | assetMapper.delete(key) |
| | } |
| | } |
| |
|
| | for (const key of changeSubscriptions.keys()) { |
| | |
| | if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) { |
| | await hooks.unsubscribeFromChanges(key) |
| | } |
| | } |
| |
|
| | for (const [key] of currentEntryIssues) { |
| | if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) { |
| | currentEntryIssues.delete(key) |
| | } |
| | } |
| |
|
| | for (const client of clients) { |
| | const state = clientStates.get(client) |
| | if (!state) { |
| | continue |
| | } |
| |
|
| | for (const key of state.clientIssues.keys()) { |
| | if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) { |
| | state.clientIssues.delete(key) |
| | } |
| | } |
| |
|
| | for (const id of state.subscriptions.keys()) { |
| | if ( |
| | !hasEntrypointForKey( |
| | currentEntrypoints, |
| | getEntryKey('assets', 'client', id), |
| | assetMapper |
| | ) |
| | ) { |
| | hooks.unsubscribeFromHmrEvents(client, id) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | export async function handlePagesErrorRoute({ |
| | currentEntryIssues, |
| | entrypoints, |
| | manifestLoader, |
| | devRewrites, |
| | productionRewrites, |
| | logErrors, |
| | hooks, |
| | }: { |
| | currentEntryIssues: EntryIssuesMap |
| | entrypoints: Entrypoints |
| | manifestLoader: TurbopackManifestLoader |
| | devRewrites: SetupOpts['fsChecker']['rewrites'] | undefined |
| | productionRewrites: CustomRoutes['rewrites'] | undefined |
| | logErrors: boolean |
| | hooks: HandleRouteTypeHooks |
| | }) { |
| | if (entrypoints.global.app) { |
| | const key = getEntryKey('pages', 'server', '_app') |
| |
|
| | const writtenEndpoint = await entrypoints.global.app.writeToDisk() |
| | hooks.handleWrittenEndpoint(key, writtenEndpoint, false) |
| | hooks.subscribeToChanges( |
| | key, |
| | false, |
| | entrypoints.global.app, |
| | () => { |
| | |
| | |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES, |
| | } |
| | }, |
| | () => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: '_app has changed (error route)', |
| | } |
| | } |
| | ) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) |
| | } |
| | await manifestLoader.loadBuildManifest('_app') |
| | await manifestLoader.loadPagesManifest('_app') |
| | await manifestLoader.loadFontManifest('_app') |
| |
|
| | if (entrypoints.global.document) { |
| | const key = getEntryKey('pages', 'server', '_document') |
| |
|
| | const writtenEndpoint = await entrypoints.global.document.writeToDisk() |
| | hooks.handleWrittenEndpoint(key, writtenEndpoint, false) |
| | hooks.subscribeToChanges( |
| | key, |
| | false, |
| | entrypoints.global.document, |
| | () => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: '_document has changed (error route)', |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in _document subscription (error route): ${e}`, |
| | } |
| | } |
| | ) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) |
| | } |
| | await manifestLoader.loadPagesManifest('_document') |
| |
|
| | if (entrypoints.global.error) { |
| | const key = getEntryKey('pages', 'server', '_error') |
| |
|
| | const writtenEndpoint = await entrypoints.global.error.writeToDisk() |
| | hooks.handleWrittenEndpoint(key, writtenEndpoint, false) |
| | hooks.subscribeToChanges( |
| | key, |
| | false, |
| | entrypoints.global.error, |
| | () => { |
| | |
| | |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES, |
| | } |
| | }, |
| | (e) => { |
| | return { |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE, |
| | data: `error in _error subscription: ${e}`, |
| | } |
| | } |
| | ) |
| | processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) |
| | } |
| | await manifestLoader.loadClientBuildManifest('_error') |
| | await manifestLoader.loadBuildManifest('_error') |
| | await manifestLoader.loadPagesManifest('_error') |
| | await manifestLoader.loadFontManifest('_error') |
| |
|
| | manifestLoader.writeManifests({ |
| | devRewrites, |
| | productionRewrites, |
| | entrypoints, |
| | }) |
| | } |
| |
|
| | export function removeRouteSuffix(route: string): string { |
| | return route.replace(/\/route$/, '') |
| | } |
| |
|
| | export function addRouteSuffix(route: string): string { |
| | return route + '/route' |
| | } |
| |
|
| | export function addMetadataIdToRoute(route: string): string { |
| | return route + '/[__metadata_id__]' |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function normalizedPageToTurbopackStructureRoute( |
| | route: string, |
| | ext: string | false |
| | ): string { |
| | let entrypointKey = route |
| | if (isMetadataRoute(entrypointKey)) { |
| | entrypointKey = entrypointKey.endsWith('/route') |
| | ? entrypointKey.slice(0, -'/route'.length) |
| | : entrypointKey |
| |
|
| | if (ext) { |
| | if (entrypointKey.endsWith('/[__metadata_id__]')) { |
| | entrypointKey = entrypointKey.slice(0, -'/[__metadata_id__]'.length) |
| | } |
| | if (entrypointKey.endsWith('/sitemap.xml') && ext !== '.xml') { |
| | |
| | entrypointKey = entrypointKey.slice(0, -'.xml'.length) |
| | } |
| | } |
| | entrypointKey = entrypointKey + '/route' |
| | } |
| | return entrypointKey |
| | } |
| |
|