diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index fd515d0866afe..7621532f16fd4 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1339,8 +1339,8 @@ export default async function build( }) // eslint-disable-next-line @typescript-eslint/no-unused-vars - const iter = project.entrypointsSubscribe() - // for await (const entrypoints of iter) { + const entrypointsSubscription = project.entrypointsSubscribe() + // for await (const entrypoints of entrypointsSubscription) { // } throw new Error("next build doesn't support turbopack yet") } diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index 2842b56a9126c..1848ffe7cd05d 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -8,9 +8,6 @@ import type { } from '../../build/swc' import type { Socket } from 'net' import type { OutputState } from '../../build/output/store' -import type { BuildManifest } from '../get-page-files' -import type { PagesManifest } from '../../build/webpack/plugins/pages-manifest-plugin' -import type { AppBuildManifest } from '../../build/webpack/plugins/app-build-manifest-plugin' import type { CompilationError, HMR_ACTION_TYPES, @@ -22,37 +19,17 @@ import type { import ws from 'next/dist/compiled/ws' import { createDefineEnv } from '../../build/swc' -import { join, posix } from 'path' +import { join } from 'path' import * as Log from '../../build/output/log' import { getVersionInfo, matchNextPageBundleRequest, } from './hot-reloader-webpack' import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths' -import { isInterceptionRouteRewrite } from '../../lib/generate-interception-routes-rewrites' import { store as consoleStore } from '../../build/output/store' - -import { - APP_BUILD_MANIFEST, - APP_PATHS_MANIFEST, - BUILD_MANIFEST, - MIDDLEWARE_MANIFEST, - NEXT_FONT_MANIFEST, - PAGES_MANIFEST, - SERVER_REFERENCE_MANIFEST, - REACT_LOADABLE_MANIFEST, - MIDDLEWARE_REACT_LOADABLE_MANIFEST, - MIDDLEWARE_BUILD_MANIFEST, - INTERCEPTION_ROUTE_REWRITE_MANIFEST, -} from '../../shared/lib/constants' import { getOverlayMiddleware } from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware-turbopack' -import { mkdir, readFile, writeFile } from 'fs/promises' +import { mkdir, writeFile } from 'fs/promises' import { PageNotFoundError } from '../../shared/lib/utils' -import { - type ClientBuildManifest, - normalizeRewritesForBuildManifest, - srcEmptySsgManifest, -} from '../../build/webpack/plugins/build-manifest-plugin' import { HMR_ACTIONS_SENT_TO_BROWSER } from './hot-reloader-types' import type { Update as TurbopackUpdate } from '../../build/swc' import { debounce } from '../utils' @@ -65,28 +42,34 @@ import { clearModuleContext, clearAllModuleContexts, } from '../lib/render-server' -import type { ActionManifest } from '../../build/webpack/plugins/flight-client-entry-plugin' import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page-path' -import type { LoadableManifest } from '../load-components' import { bold, green, magenta, red } from '../../lib/picocolors' -import { writeFileAtomic } from '../../lib/fs/write-atomic' import { trace } from '../../trace' import type { VersionInfo } from './parse-version-info' -import type { NextFontManifest } from '../../build/webpack/plugins/next-font-manifest-plugin' import { MAGIC_IDENTIFIER_REGEX, decodeMagicIdentifier, } from '../../shared/lib/magic-identifier' import { getTurbopackJsConfig, - mergeActionManifests, - mergeAppBuildManifests, - mergeBuildManifests, - mergeFontManifests, - mergeLoadableManifests, - mergeMiddlewareManifests, - mergePagesManifests, - type TurbopackMiddlewareManifest, + type BuildManifests, + type AppBuildManifests, + type PagesManifests, + type AppPathsManifests, + type MiddlewareManifests, + type ActionManifests, + type FontManifests, + type LoadableManifests, + loadMiddlewareManifest, + writeManifests, + loadBuildManifest, + loadPagesManifest, + loadLoadableManifest, + loadFontManifest, + loadAppBuildManifest, + loadAppPathManifest, + loadActionManifest, + type CurrentEntrypoints, } from './turbopack-utils' import { propagateServerField, @@ -187,16 +170,16 @@ function formatIssue(issue: Issue) { return message } -type Issues = Map> +type CurrentIssues = Map> function processIssues( - issues: Issues, + currentIssues: CurrentIssues, name: string, result: TurbopackResult, throwIssue = false ) { const newIssues = new Map() - issues.set(name, newIssues) + currentIssues.set(name, newIssues) const relevantIssues = new Set() @@ -270,14 +253,14 @@ export async function createHotReloaderTurbopack( }), serverAddr: `127.0.0.1:${opts.port}`, }) - const iter = project.entrypointsSubscribe() - const curEntries: Map = new Map() + const entrypointsSubscription = project.entrypointsSubscribe() + const currentEntrypoints: CurrentEntrypoints = new Map() const changeSubscriptions: Map< string, Promise> > = new Map() let prevMiddleware: boolean | undefined = undefined - const globalEntries: { + const globalEntrypoints: { app: Endpoint | undefined document: Endpoint | undefined error: Endpoint | undefined @@ -293,7 +276,7 @@ export async function createHotReloaderTurbopack( const hmrPayloads = new Map() const turbopackUpdates: TurbopackUpdate[] = [] - const issues: Issues = new Map() + const currentIssues: CurrentIssues = new Map() const serverPathState = new Map() async function handleRequireCacheClearing( @@ -391,7 +374,7 @@ export async function createHotReloaderTurbopack( let hmrEventHappened = false let hmrHash = 0 const sendEnqueuedMessages = () => { - for (const [, issueMap] of issues) { + for (const [, issueMap] of currentIssues) { if (issueMap.size > 0) { // During compilation errors we want to delay the HMR events until errors are fixed return @@ -411,8 +394,8 @@ export async function createHotReloaderTurbopack( } const sendEnqueuedMessagesDebounce = debounce(sendEnqueuedMessages, 2) - function sendHmr(key: string, id: string, payload: HMR_ACTION_TYPES) { - hmrPayloads.set(`${key}:${id}`, payload) + function sendHmr(id: string, payload: HMR_ACTION_TYPES) { + hmrPayloads.set(`${id}`, payload) hmrEventHappened = true sendEnqueuedMessagesDebounce() } @@ -423,127 +406,22 @@ export async function createHotReloaderTurbopack( sendEnqueuedMessagesDebounce() } - async function loadPartialManifest( - name: - | typeof MIDDLEWARE_MANIFEST - | typeof BUILD_MANIFEST - | typeof APP_BUILD_MANIFEST - | typeof PAGES_MANIFEST - | typeof APP_PATHS_MANIFEST - | `${typeof SERVER_REFERENCE_MANIFEST}.json` - | `${typeof NEXT_FONT_MANIFEST}.json` - | typeof REACT_LOADABLE_MANIFEST, - pageName: string, - type: - | 'pages' - | 'app' - | 'app-route' - | 'middleware' - | 'instrumentation' = 'pages' - ): Promise { - const manifestPath = posix.join( - distDir, - `server`, - type === 'app-route' ? 'app' : type, - type === 'middleware' || type === 'instrumentation' - ? '' - : pageName === '/' - ? 'index' - : pageName === '/index' || pageName.startsWith('/index/') - ? `/index${pageName}` - : pageName, - type === 'app' ? 'page' : type === 'app-route' ? 'route' : '', - name - ) - return JSON.parse(await readFile(posix.join(manifestPath), 'utf-8')) as T - } + const buildManifests: BuildManifests = new Map() + const appBuildManifests: AppBuildManifests = new Map() + const pagesManifests: PagesManifests = new Map() + const appPathsManifests: AppPathsManifests = new Map() + const middlewareManifests: MiddlewareManifests = new Map() + const actionManifests: ActionManifests = new Map() + const fontManifests: FontManifests = new Map() + const loadableManifests: LoadableManifests = new Map() + + const clientToHmrSubscription: Map< + ws, + Map> + > = new Map() - const buildManifests = new Map() - const appBuildManifests = new Map() - const pagesManifests = new Map() - const appPathsManifests = new Map() - const middlewareManifests = new Map() - const actionManifests = new Map() - const fontManifests = new Map() - const loadableManifests = new Map() - const clientToHmrSubscription = new Map>>() const clients = new Set() - async function loadMiddlewareManifest( - pageName: string, - type: 'pages' | 'app' | 'app-route' | 'middleware' | 'instrumentation' - ): Promise { - middlewareManifests.set( - pageName, - await loadPartialManifest(MIDDLEWARE_MANIFEST, pageName, type) - ) - } - - async function loadBuildManifest( - pageName: string, - type: 'app' | 'pages' = 'pages' - ): Promise { - buildManifests.set( - pageName, - await loadPartialManifest(BUILD_MANIFEST, pageName, type) - ) - } - - async function loadAppBuildManifest(pageName: string): Promise { - appBuildManifests.set( - pageName, - await loadPartialManifest(APP_BUILD_MANIFEST, pageName, 'app') - ) - } - - async function loadPagesManifest(pageName: string): Promise { - pagesManifests.set( - pageName, - await loadPartialManifest(PAGES_MANIFEST, pageName) - ) - } - - async function loadAppPathManifest( - pageName: string, - type: 'app' | 'app-route' = 'app' - ): Promise { - appPathsManifests.set( - pageName, - await loadPartialManifest(APP_PATHS_MANIFEST, pageName, type) - ) - } - - async function loadActionManifest(pageName: string): Promise { - actionManifests.set( - pageName, - await loadPartialManifest( - `${SERVER_REFERENCE_MANIFEST}.json`, - pageName, - 'app' - ) - ) - } - - async function loadFontManifest( - pageName: string, - type: 'app' | 'pages' = 'pages' - ): Promise { - fontManifests.set( - pageName, - await loadPartialManifest(`${NEXT_FONT_MANIFEST}.json`, pageName, type) - ) - } - - async function loadLoadableManifest( - pageName: string, - type: 'app' | 'pages' = 'pages' - ): Promise { - loadableManifests.set( - pageName, - await loadPartialManifest(REACT_LOADABLE_MANIFEST, pageName, type) - ) - } - async function changeSubscription( page: string, type: 'client' | 'server', @@ -562,10 +440,10 @@ export async function createHotReloaderTurbopack( const changed = await changedPromise for await (const change of changed) { - processIssues(issues, page, change) + processIssues(currentIssues, page, change) const payload = await makePayload(page, change) if (payload) { - sendHmr('endpoint-change', key, payload) + sendHmr(key, payload) } } } @@ -580,207 +458,7 @@ export async function createHotReloaderTurbopack( subscription.return?.() changeSubscriptions.delete(key) } - issues.delete(key) - } - - async function writeBuildManifest( - rewrites: SetupOpts['fsChecker']['rewrites'] - ): Promise { - const buildManifest = mergeBuildManifests(buildManifests.values()) - const buildManifestPath = join(distDir, BUILD_MANIFEST) - const middlewareBuildManifestPath = join( - distDir, - 'server', - `${MIDDLEWARE_BUILD_MANIFEST}.js` - ) - const interceptionRewriteManifestPath = join( - distDir, - 'server', - `${INTERCEPTION_ROUTE_REWRITE_MANIFEST}.js` - ) - deleteCache(buildManifestPath) - deleteCache(middlewareBuildManifestPath) - deleteCache(interceptionRewriteManifestPath) - await writeFileAtomic( - buildManifestPath, - JSON.stringify(buildManifest, null, 2) - ) - await writeFileAtomic( - middlewareBuildManifestPath, - `self.__BUILD_MANIFEST=${JSON.stringify(buildManifest)};` - ) - - const interceptionRewrites = JSON.stringify( - rewrites.beforeFiles.filter(isInterceptionRouteRewrite) - ) - - await writeFileAtomic( - interceptionRewriteManifestPath, - `self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST=${JSON.stringify( - interceptionRewrites - )};` - ) - - const content: ClientBuildManifest = { - __rewrites: rewrites - ? (normalizeRewritesForBuildManifest(rewrites) as any) - : { afterFiles: [], beforeFiles: [], fallback: [] }, - ...Object.fromEntries( - [...curEntries.keys()].map((pathname) => [ - pathname, - `static/chunks/pages${pathname === '/' ? '/index' : pathname}.js`, - ]) - ), - sortedPages: [...curEntries.keys()], - } - const buildManifestJs = `self.__BUILD_MANIFEST = ${JSON.stringify( - content - )};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()` - await writeFileAtomic( - join(distDir, 'static', 'development', '_buildManifest.js'), - buildManifestJs - ) - await writeFileAtomic( - join(distDir, 'static', 'development', '_ssgManifest.js'), - srcEmptySsgManifest - ) - } - - async function writeFallbackBuildManifest(): Promise { - const fallbackBuildManifest = mergeBuildManifests( - [buildManifests.get('_app'), buildManifests.get('_error')].filter( - Boolean - ) as BuildManifest[] - ) - const fallbackBuildManifestPath = join( - distDir, - `fallback-${BUILD_MANIFEST}` - ) - deleteCache(fallbackBuildManifestPath) - await writeFileAtomic( - fallbackBuildManifestPath, - JSON.stringify(fallbackBuildManifest, null, 2) - ) - } - - async function writeAppBuildManifest(): Promise { - const appBuildManifest = mergeAppBuildManifests(appBuildManifests.values()) - const appBuildManifestPath = join(distDir, APP_BUILD_MANIFEST) - deleteCache(appBuildManifestPath) - await writeFileAtomic( - appBuildManifestPath, - JSON.stringify(appBuildManifest, null, 2) - ) - } - - async function writePagesManifest(): Promise { - const pagesManifest = mergePagesManifests(pagesManifests.values()) - const pagesManifestPath = join(distDir, 'server', PAGES_MANIFEST) - deleteCache(pagesManifestPath) - await writeFileAtomic( - pagesManifestPath, - JSON.stringify(pagesManifest, null, 2) - ) - } - - async function writeAppPathsManifest(): Promise { - const appPathsManifest = mergePagesManifests(appPathsManifests.values()) - const appPathsManifestPath = join(distDir, 'server', APP_PATHS_MANIFEST) - deleteCache(appPathsManifestPath) - await writeFileAtomic( - appPathsManifestPath, - JSON.stringify(appPathsManifest, null, 2) - ) - } - - async function writeMiddlewareManifest(): Promise { - const middlewareManifest = mergeMiddlewareManifests( - middlewareManifests.values() - ) - const middlewareManifestPath = join(distDir, 'server', MIDDLEWARE_MANIFEST) - deleteCache(middlewareManifestPath) - await writeFileAtomic( - middlewareManifestPath, - JSON.stringify(middlewareManifest, null, 2) - ) - } - - async function writeActionManifest(): Promise { - const actionManifest = await mergeActionManifests(actionManifests.values()) - const actionManifestJsonPath = join( - distDir, - 'server', - `${SERVER_REFERENCE_MANIFEST}.json` - ) - const actionManifestJsPath = join( - distDir, - 'server', - `${SERVER_REFERENCE_MANIFEST}.js` - ) - const json = JSON.stringify(actionManifest, null, 2) - deleteCache(actionManifestJsonPath) - deleteCache(actionManifestJsPath) - await writeFile(actionManifestJsonPath, json, 'utf-8') - await writeFile( - actionManifestJsPath, - `self.__RSC_SERVER_MANIFEST=${JSON.stringify(json)}`, - 'utf-8' - ) - } - - async function writeFontManifest(): Promise { - const fontManifest = mergeFontManifests(fontManifests.values()) - const json = JSON.stringify(fontManifest, null, 2) - - const fontManifestJsonPath = join( - distDir, - 'server', - `${NEXT_FONT_MANIFEST}.json` - ) - const fontManifestJsPath = join( - distDir, - 'server', - `${NEXT_FONT_MANIFEST}.js` - ) - deleteCache(fontManifestJsonPath) - deleteCache(fontManifestJsPath) - await writeFileAtomic(fontManifestJsonPath, json) - await writeFileAtomic( - fontManifestJsPath, - `self.__NEXT_FONT_MANIFEST=${JSON.stringify(json)}` - ) - } - - async function writeLoadableManifest(): Promise { - const loadableManifest = mergeLoadableManifests(loadableManifests.values()) - const loadableManifestPath = join(distDir, REACT_LOADABLE_MANIFEST) - const middlewareloadableManifestPath = join( - distDir, - 'server', - `${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js` - ) - - const json = JSON.stringify(loadableManifest, null, 2) - - deleteCache(loadableManifestPath) - deleteCache(middlewareloadableManifestPath) - await writeFileAtomic(loadableManifestPath, json) - await writeFileAtomic( - middlewareloadableManifestPath, - `self.__REACT_LOADABLE_MANIFEST=${JSON.stringify(json)}` - ) - } - - async function writeManifests(): Promise { - await writeBuildManifest(opts.fsChecker.rewrites) - await writeAppBuildManifest() - await writePagesManifest() - await writeAppPathsManifest() - await writeMiddlewareManifest() - await writeActionManifest() - await writeFontManifest() - await writeLoadableManifest() - await writeFallbackBuildManifest() + currentIssues.delete(key) } async function subscribeToHmrEvents(id: string, client: ws) { @@ -800,7 +478,7 @@ export async function createHotReloaderTurbopack( await subscription.next() for await (const data of subscription) { - processIssues(issues, id, data) + processIssues(currentIssues, id, data) if (data.type !== 'issues') { sendTurbopackMessage(data) } @@ -826,19 +504,19 @@ export async function createHotReloaderTurbopack( } try { - async function handleEntries() { - for await (const entrypoints of iter) { + async function handleEntrypointsSubscription() { + for await (const entrypoints of entrypointsSubscription) { if (!currentEntriesHandlingResolve) { currentEntriesHandling = new Promise( // eslint-disable-next-line no-loop-func (resolve) => (currentEntriesHandlingResolve = resolve) ) } - globalEntries.app = entrypoints.pagesAppEndpoint - globalEntries.document = entrypoints.pagesDocumentEndpoint - globalEntries.error = entrypoints.pagesErrorEndpoint + globalEntrypoints.app = entrypoints.pagesAppEndpoint + globalEntrypoints.document = entrypoints.pagesDocumentEndpoint + globalEntrypoints.error = entrypoints.pagesErrorEndpoint - curEntries.clear() + currentEntrypoints.clear() for (const [pathname, route] of entrypoints.routes) { switch (route.type) { @@ -846,7 +524,7 @@ export async function createHotReloaderTurbopack( case 'page-api': case 'app-page': case 'app-route': { - curEntries.set(pathname, route) + currentEntrypoints.set(pathname, route) break } default: @@ -861,7 +539,7 @@ export async function createHotReloaderTurbopack( continue } - if (!curEntries.has(pathname)) { + if (!currentEntrypoints.has(pathname)) { const subscription = await subscriptionPromise subscription.return?.() changeSubscriptions.delete(pathname) @@ -875,12 +553,12 @@ export async function createHotReloaderTurbopack( if (prevMiddleware === true && !middleware) { // Went from middleware to no middleware await clearChangeSubscription('middleware', 'server') - sendHmr('entrypoint-change', 'middleware', { + sendHmr('middleware', { event: HMR_ACTIONS_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, }) } else if (prevMiddleware === false && middleware) { // Went from no middleware to middleware - sendHmr('endpoint-change', 'middleware', { + sendHmr('middleware', { event: HMR_ACTIONS_SENT_TO_BROWSER.MIDDLEWARE_CHANGES, }) } @@ -897,7 +575,7 @@ export async function createHotReloaderTurbopack( displayName, await instrumentation[prop].writeToDisk() ) - processIssues(issues, name, writtenEndpoint) + processIssues(currentIssues, name, writtenEndpoint) } await processInstrumentation( 'instrumentation (node.js)', @@ -909,8 +587,25 @@ export async function createHotReloaderTurbopack( 'instrumentation.edge', 'edge' ) - await loadMiddlewareManifest('instrumentation', 'instrumentation') - await writeManifests() + await loadMiddlewareManifest( + distDir, + middlewareManifests, + 'instrumentation', + 'instrumentation' + ) + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) serverFields.actualInstrumentationHookFile = '/instrumentation' await propagateServerField( @@ -932,8 +627,13 @@ export async function createHotReloaderTurbopack( 'middleware', await middleware.endpoint.writeToDisk() ) - processIssues(issues, 'middleware', writtenEndpoint) - await loadMiddlewareManifest('middleware', 'middleware') + processIssues(currentIssues, 'middleware', writtenEndpoint) + await loadMiddlewareManifest( + distDir, + middlewareManifests, + 'middleware', + 'middleware' + ) serverFields.middleware = { match: null as any, page: '/', @@ -965,7 +665,19 @@ export async function createHotReloaderTurbopack( 'middleware', serverFields.middleware ) - await writeManifests() + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) finishBuilding() return { event: HMR_ACTIONS_SENT_TO_BROWSER.MIDDLEWARE_CHANGES } @@ -990,7 +702,7 @@ export async function createHotReloaderTurbopack( } } - handleEntries().catch((err) => { + handleEntrypointsSubscription().catch((err) => { console.error(err) process.exit(1) }) @@ -1012,8 +724,19 @@ export async function createHotReloaderTurbopack( ) ) await currentEntriesHandling - await writeManifests() - + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) const overlayMiddleware = getOverlayMiddleware(project) const versionInfo: VersionInfo = await getVersionInfo( isTestMode || opts.telemetry.isEnabled @@ -1031,24 +754,24 @@ export async function createHotReloaderTurbopack( case 'page': { finishBuilding = startBuilding(page, requestUrl) try { - if (globalEntries.app) { + if (globalEntrypoints.app) { const writtenEndpoint = await handleRequireCacheClearing( '_app', - await globalEntries.app.writeToDisk() + await globalEntrypoints.app.writeToDisk() ) - processIssues(issues, '_app', writtenEndpoint) + processIssues(currentIssues, '_app', writtenEndpoint) } - await loadBuildManifest('_app') - await loadPagesManifest('_app') + await loadBuildManifest(distDir, buildManifests, '_app') + await loadPagesManifest(distDir, pagesManifests, '_app') - if (globalEntries.document) { + if (globalEntrypoints.document) { const writtenEndpoint = await handleRequireCacheClearing( '_document', - await globalEntries.document.writeToDisk() + await globalEntrypoints.document.writeToDisk() ) - processIssues(issues, '_document', writtenEndpoint) + processIssues(currentIssues, '_document', writtenEndpoint) } - await loadPagesManifest('_document') + await loadPagesManifest(distDir, pagesManifests, '_document') const writtenEndpoint = await handleRequireCacheClearing( page, @@ -1057,19 +780,41 @@ export async function createHotReloaderTurbopack( const type = writtenEndpoint?.type - await loadBuildManifest(page) - await loadPagesManifest(page) + await loadBuildManifest(distDir, buildManifests, page) + await loadPagesManifest(distDir, pagesManifests, page) if (type === 'edge') { - await loadMiddlewareManifest(page, 'pages') + await loadMiddlewareManifest( + distDir, + middlewareManifests, + page, + 'pages' + ) } else { middlewareManifests.delete(page) } - await loadFontManifest(page, 'pages') - await loadLoadableManifest(page, 'pages') + await loadFontManifest(distDir, fontManifests, page, 'pages') + await loadLoadableManifest( + distDir, + loadableManifests, + page, + 'pages' + ) - await writeManifests() + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) - processIssues(issues, page, writtenEndpoint) + processIssues(currentIssues, page, writtenEndpoint) } finally { changeSubscription( page, @@ -1096,12 +841,12 @@ export async function createHotReloaderTurbopack( } } ) - if (globalEntries.document) { + if (globalEntrypoints.document) { changeSubscription( '_document', 'server', false, - globalEntries.document, + globalEntrypoints.document, () => { return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } } @@ -1120,17 +865,34 @@ export async function createHotReloaderTurbopack( const type = writtenEndpoint?.type - await loadPagesManifest(page) + await loadPagesManifest(distDir, pagesManifests, page) if (type === 'edge') { - await loadMiddlewareManifest(page, 'pages') + await loadMiddlewareManifest( + distDir, + middlewareManifests, + page, + 'pages' + ) } else { middlewareManifests.delete(page) } - await loadLoadableManifest(page, 'pages') + await loadLoadableManifest(distDir, loadableManifests, page, 'pages') - await writeManifests() + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) - processIssues(issues, page, writtenEndpoint) + processIssues(currentIssues, page, writtenEndpoint) break } @@ -1163,19 +925,36 @@ export async function createHotReloaderTurbopack( const type = writtenEndpoint?.type if (type === 'edge') { - await loadMiddlewareManifest(page, 'app') + await loadMiddlewareManifest( + distDir, + middlewareManifests, + page, + 'app' + ) } else { middlewareManifests.delete(page) } - await loadAppBuildManifest(page) - await loadBuildManifest(page, 'app') - await loadAppPathManifest(page, 'app') - await loadActionManifest(page) - await loadFontManifest(page, 'app') - await writeManifests() + await loadAppBuildManifest(distDir, appBuildManifests, page) + await loadBuildManifest(distDir, buildManifests, page, 'app') + await loadAppPathManifest(distDir, appPathsManifests, page, 'app') + await loadActionManifest(distDir, actionManifests, page) + await loadFontManifest(distDir, fontManifests, page, 'app') + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) - processIssues(issues, page, writtenEndpoint, true) + processIssues(currentIssues, page, writtenEndpoint, true) break } @@ -1188,16 +967,37 @@ export async function createHotReloaderTurbopack( const type = writtenEndpoint?.type - await loadAppPathManifest(page, 'app-route') + await loadAppPathManifest( + distDir, + appPathsManifests, + page, + 'app-route' + ) if (type === 'edge') { - await loadMiddlewareManifest(page, 'app-route') + await loadMiddlewareManifest( + distDir, + middlewareManifests, + page, + 'app-route' + ) } else { middlewareManifests.delete(page) } - await writeManifests() - - processIssues(issues, page, writtenEndpoint, true) + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) + processIssues(currentIssues, page, writtenEndpoint, true) break } @@ -1324,7 +1124,7 @@ export async function createHotReloaderTurbopack( client.send(JSON.stringify(turbopackConnected)) const errors = [] - for (const pageIssues of issues.values()) { + for (const pageIssues of currentIssues.values()) { for (const issue of pageIssues.values()) { errors.push({ message: formatIssue(issue), @@ -1362,7 +1162,7 @@ export async function createHotReloaderTurbopack( // Not implemented yet. }, async getCompilationErrors(page) { - const thisPageIssues = issues.get(page) + const thisPageIssues = currentIssues.get(page) if (thisPageIssues !== undefined && thisPageIssues.size > 0) { // If there is an error related to the requesting page we display it instead of the first error return [...thisPageIssues.values()].map( @@ -1372,7 +1172,7 @@ export async function createHotReloaderTurbopack( // Otherwise, return all errors across pages const errors = [] - for (const pageIssues of issues.values()) { + for (const pageIssues of currentIssues.values()) { for (const issue of pageIssues.values()) { errors.push(new Error(formatIssue(issue))) } @@ -1407,47 +1207,59 @@ export async function createHotReloaderTurbopack( if (page === '/_error') { let finishBuilding = startBuilding(page, requestUrl) try { - if (globalEntries.app) { + if (globalEntrypoints.app) { const writtenEndpoint = await handleRequireCacheClearing( '_app', - await globalEntries.app.writeToDisk() + await globalEntrypoints.app.writeToDisk() ) - processIssues(issues, '_app', writtenEndpoint) + processIssues(currentIssues, '_app', writtenEndpoint) } - await loadBuildManifest('_app') - await loadPagesManifest('_app') - await loadFontManifest('_app') + await loadBuildManifest(distDir, buildManifests, '_app') + await loadPagesManifest(distDir, pagesManifests, '_app') + await loadFontManifest(distDir, fontManifests, '_app') - if (globalEntries.document) { + if (globalEntrypoints.document) { const writtenEndpoint = await handleRequireCacheClearing( '_document', - await globalEntries.document.writeToDisk() + await globalEntrypoints.document.writeToDisk() ) changeSubscription( '_document', 'server', false, - globalEntries.document, + globalEntrypoints.document, () => { return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } } ) - processIssues(issues, '_document', writtenEndpoint) + processIssues(currentIssues, '_document', writtenEndpoint) } - await loadPagesManifest('_document') + await loadPagesManifest(distDir, pagesManifests, '_document') - if (globalEntries.error) { + if (globalEntrypoints.error) { const writtenEndpoint = await handleRequireCacheClearing( '_error', - await globalEntries.error.writeToDisk() + await globalEntrypoints.error.writeToDisk() ) - processIssues(issues, page, writtenEndpoint) + processIssues(currentIssues, page, writtenEndpoint) } - await loadBuildManifest('_error') - await loadPagesManifest('_error') - await loadFontManifest('_error') + await loadBuildManifest(distDir, buildManifests, '_error') + await loadPagesManifest(distDir, pagesManifests, '_error') + await loadFontManifest(distDir, fontManifests, '_error') - await writeManifests() + await writeManifests( + opts, + distDir, + buildManifests, + appBuildManifests, + pagesManifests, + appPathsManifests, + middlewareManifests, + actionManifests, + fontManifests, + loadableManifests, + currentEntrypoints + ) } finally { finishBuilding() } @@ -1455,8 +1267,8 @@ export async function createHotReloaderTurbopack( } await currentEntriesHandling const route = - curEntries.get(page) ?? - curEntries.get( + currentEntrypoints.get(page) ?? + currentEntrypoints.get( normalizeAppPath( normalizeMetadataRoute(definition?.page ?? inputPage) ) @@ -1496,7 +1308,7 @@ export async function createHotReloaderTurbopack( sendEnqueuedMessages() const errors = new Map() - for (const [, issueMap] of issues) { + for (const [, issueMap] of currentIssues) { for (const [key, issue] of issueMap) { if (errors.has(key)) continue diff --git a/packages/next/src/server/dev/turbopack-utils.ts b/packages/next/src/server/dev/turbopack-utils.ts index 8a5744abb9447..42ff28ac12766 100644 --- a/packages/next/src/server/dev/turbopack-utils.ts +++ b/packages/next/src/server/dev/turbopack-utils.ts @@ -12,6 +12,31 @@ import type { AppBuildManifest } from '../../build/webpack/plugins/app-build-man import type { BuildManifest } from '../get-page-files' import type { NextConfigComplete } from '../config-shared' import loadJsConfig from '../../build/load-jsconfig' +import { join, posix } from 'path' +import { readFile, writeFile } from 'fs/promises' +import { + APP_BUILD_MANIFEST, + APP_PATHS_MANIFEST, + BUILD_MANIFEST, + MIDDLEWARE_MANIFEST, + NEXT_FONT_MANIFEST, + PAGES_MANIFEST, + SERVER_REFERENCE_MANIFEST, + REACT_LOADABLE_MANIFEST, + MIDDLEWARE_BUILD_MANIFEST, + INTERCEPTION_ROUTE_REWRITE_MANIFEST, + MIDDLEWARE_REACT_LOADABLE_MANIFEST, +} from '../../shared/lib/constants' +import { writeFileAtomic } from '../../lib/fs/write-atomic' +import { deleteCache } from '../../build/webpack/plugins/nextjs-require-cache-hot-reloader' +import { + normalizeRewritesForBuildManifest, + type ClientBuildManifest, + srcEmptySsgManifest, +} from '../../build/webpack/plugins/build-manifest-plugin' +import type { SetupOpts } from '../lib/router-utils/setup-dev-bundler' +import { isInterceptionRouteRewrite } from '../../lib/generate-interception-routes-rewrites' +import type { Route } from '../../build/swc' export interface InstrumentationDefinition { files: string[] @@ -179,3 +204,389 @@ export async function getTurbopackJsConfig( const { jsConfig } = await loadJsConfig(dir, nextConfig) return jsConfig ?? { compilerOptions: {} } } + +export async function readPartialManifest( + distDir: string, + name: + | typeof MIDDLEWARE_MANIFEST + | typeof BUILD_MANIFEST + | typeof APP_BUILD_MANIFEST + | typeof PAGES_MANIFEST + | typeof APP_PATHS_MANIFEST + | `${typeof SERVER_REFERENCE_MANIFEST}.json` + | `${typeof NEXT_FONT_MANIFEST}.json` + | typeof REACT_LOADABLE_MANIFEST, + pageName: string, + type: + | 'pages' + | 'app' + | 'app-route' + | 'middleware' + | 'instrumentation' = 'pages' +): Promise { + const manifestPath = posix.join( + distDir, + `server`, + type === 'app-route' ? 'app' : type, + type === 'middleware' || type === 'instrumentation' + ? '' + : pageName === '/' + ? 'index' + : pageName === '/index' || pageName.startsWith('/index/') + ? `/index${pageName}` + : pageName, + type === 'app' ? 'page' : type === 'app-route' ? 'route' : '', + name + ) + return JSON.parse(await readFile(posix.join(manifestPath), 'utf-8')) as T +} + +export type BuildManifests = Map +export type AppBuildManifests = Map +export type PagesManifests = Map +export type AppPathsManifests = Map +export type MiddlewareManifests = Map +export type ActionManifests = Map +export type FontManifests = Map +export type LoadableManifests = Map +export type CurrentEntrypoints = Map + +export async function loadMiddlewareManifest( + distDir: string, + middlewareManifests: MiddlewareManifests, + pageName: string, + type: 'pages' | 'app' | 'app-route' | 'middleware' | 'instrumentation' +): Promise { + middlewareManifests.set( + pageName, + await readPartialManifest(distDir, MIDDLEWARE_MANIFEST, pageName, type) + ) +} + +export async function loadBuildManifest( + distDir: string, + buildManifests: BuildManifests, + pageName: string, + type: 'app' | 'pages' = 'pages' +): Promise { + buildManifests.set( + pageName, + await readPartialManifest(distDir, BUILD_MANIFEST, pageName, type) + ) +} + +export async function loadAppBuildManifest( + distDir: string, + appBuildManifests: AppBuildManifests, + pageName: string +): Promise { + appBuildManifests.set( + pageName, + await readPartialManifest(distDir, APP_BUILD_MANIFEST, pageName, 'app') + ) +} + +export async function loadPagesManifest( + distDir: string, + pagesManifests: PagesManifests, + pageName: string +): Promise { + pagesManifests.set( + pageName, + await readPartialManifest(distDir, PAGES_MANIFEST, pageName) + ) +} + +export async function loadAppPathManifest( + distDir: string, + appPathsManifests: AppPathsManifests, + pageName: string, + type: 'app' | 'app-route' = 'app' +): Promise { + appPathsManifests.set( + pageName, + await readPartialManifest(distDir, APP_PATHS_MANIFEST, pageName, type) + ) +} + +export async function loadActionManifest( + distDir: string, + actionManifests: ActionManifests, + pageName: string +): Promise { + actionManifests.set( + pageName, + await readPartialManifest( + distDir, + `${SERVER_REFERENCE_MANIFEST}.json`, + pageName, + 'app' + ) + ) +} + +export async function loadFontManifest( + distDir: string, + fontManifests: FontManifests, + pageName: string, + type: 'app' | 'pages' = 'pages' +): Promise { + fontManifests.set( + pageName, + await readPartialManifest( + distDir, + `${NEXT_FONT_MANIFEST}.json`, + pageName, + type + ) + ) +} + +export async function loadLoadableManifest( + distDir: string, + loadableManifests: LoadableManifests, + pageName: string, + type: 'app' | 'pages' = 'pages' +): Promise { + loadableManifests.set( + pageName, + await readPartialManifest(distDir, REACT_LOADABLE_MANIFEST, pageName, type) + ) +} + +async function writeBuildManifest( + distDir: string, + buildManifests: BuildManifests, + currentEntrypoints: CurrentEntrypoints, + rewrites: SetupOpts['fsChecker']['rewrites'] +): Promise { + const buildManifest = mergeBuildManifests(buildManifests.values()) + const buildManifestPath = join(distDir, BUILD_MANIFEST) + const middlewareBuildManifestPath = join( + distDir, + 'server', + `${MIDDLEWARE_BUILD_MANIFEST}.js` + ) + const interceptionRewriteManifestPath = join( + distDir, + 'server', + `${INTERCEPTION_ROUTE_REWRITE_MANIFEST}.js` + ) + deleteCache(buildManifestPath) + deleteCache(middlewareBuildManifestPath) + deleteCache(interceptionRewriteManifestPath) + await writeFileAtomic( + buildManifestPath, + JSON.stringify(buildManifest, null, 2) + ) + await writeFileAtomic( + middlewareBuildManifestPath, + `self.__BUILD_MANIFEST=${JSON.stringify(buildManifest)};` + ) + + const interceptionRewrites = JSON.stringify( + rewrites.beforeFiles.filter(isInterceptionRouteRewrite) + ) + + await writeFileAtomic( + interceptionRewriteManifestPath, + `self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST=${JSON.stringify( + interceptionRewrites + )};` + ) + + const content: ClientBuildManifest = { + __rewrites: rewrites + ? (normalizeRewritesForBuildManifest(rewrites) as any) + : { afterFiles: [], beforeFiles: [], fallback: [] }, + ...Object.fromEntries( + [...currentEntrypoints.keys()].map((pathname) => [ + pathname, + `static/chunks/pages${pathname === '/' ? '/index' : pathname}.js`, + ]) + ), + sortedPages: [...currentEntrypoints.keys()], + } + const buildManifestJs = `self.__BUILD_MANIFEST = ${JSON.stringify( + content + )};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()` + await writeFileAtomic( + join(distDir, 'static', 'development', '_buildManifest.js'), + buildManifestJs + ) + await writeFileAtomic( + join(distDir, 'static', 'development', '_ssgManifest.js'), + srcEmptySsgManifest + ) +} + +async function writeFallbackBuildManifest( + distDir: string, + buildManifests: BuildManifests +): Promise { + const fallbackBuildManifest = mergeBuildManifests( + [buildManifests.get('_app'), buildManifests.get('_error')].filter( + Boolean + ) as BuildManifest[] + ) + const fallbackBuildManifestPath = join(distDir, `fallback-${BUILD_MANIFEST}`) + deleteCache(fallbackBuildManifestPath) + await writeFileAtomic( + fallbackBuildManifestPath, + JSON.stringify(fallbackBuildManifest, null, 2) + ) +} + +async function writeAppBuildManifest( + distDir: string, + appBuildManifests: AppBuildManifests +): Promise { + const appBuildManifest = mergeAppBuildManifests(appBuildManifests.values()) + const appBuildManifestPath = join(distDir, APP_BUILD_MANIFEST) + deleteCache(appBuildManifestPath) + await writeFileAtomic( + appBuildManifestPath, + JSON.stringify(appBuildManifest, null, 2) + ) +} + +async function writePagesManifest( + distDir: string, + pagesManifests: PagesManifests +): Promise { + const pagesManifest = mergePagesManifests(pagesManifests.values()) + const pagesManifestPath = join(distDir, 'server', PAGES_MANIFEST) + deleteCache(pagesManifestPath) + await writeFileAtomic( + pagesManifestPath, + JSON.stringify(pagesManifest, null, 2) + ) +} + +async function writeAppPathsManifest( + distDir: string, + appPathsManifests: AppPathsManifests +): Promise { + const appPathsManifest = mergePagesManifests(appPathsManifests.values()) + const appPathsManifestPath = join(distDir, 'server', APP_PATHS_MANIFEST) + deleteCache(appPathsManifestPath) + await writeFileAtomic( + appPathsManifestPath, + JSON.stringify(appPathsManifest, null, 2) + ) +} + +async function writeMiddlewareManifest( + distDir: string, + middlewareManifests: MiddlewareManifests +): Promise { + const middlewareManifest = mergeMiddlewareManifests( + middlewareManifests.values() + ) + const middlewareManifestPath = join(distDir, 'server', MIDDLEWARE_MANIFEST) + deleteCache(middlewareManifestPath) + await writeFileAtomic( + middlewareManifestPath, + JSON.stringify(middlewareManifest, null, 2) + ) +} + +async function writeActionManifest( + distDir: string, + actionManifests: ActionManifests +): Promise { + const actionManifest = await mergeActionManifests(actionManifests.values()) + const actionManifestJsonPath = join( + distDir, + 'server', + `${SERVER_REFERENCE_MANIFEST}.json` + ) + const actionManifestJsPath = join( + distDir, + 'server', + `${SERVER_REFERENCE_MANIFEST}.js` + ) + const json = JSON.stringify(actionManifest, null, 2) + deleteCache(actionManifestJsonPath) + deleteCache(actionManifestJsPath) + await writeFile(actionManifestJsonPath, json, 'utf-8') + await writeFile( + actionManifestJsPath, + `self.__RSC_SERVER_MANIFEST=${JSON.stringify(json)}`, + 'utf-8' + ) +} + +async function writeFontManifest( + distDir: string, + fontManifests: FontManifests +): Promise { + const fontManifest = mergeFontManifests(fontManifests.values()) + const json = JSON.stringify(fontManifest, null, 2) + + const fontManifestJsonPath = join( + distDir, + 'server', + `${NEXT_FONT_MANIFEST}.json` + ) + const fontManifestJsPath = join(distDir, 'server', `${NEXT_FONT_MANIFEST}.js`) + deleteCache(fontManifestJsonPath) + deleteCache(fontManifestJsPath) + await writeFileAtomic(fontManifestJsonPath, json) + await writeFileAtomic( + fontManifestJsPath, + `self.__NEXT_FONT_MANIFEST=${JSON.stringify(json)}` + ) +} + +async function writeLoadableManifest( + distDir: string, + loadableManifests: LoadableManifests +): Promise { + const loadableManifest = mergeLoadableManifests(loadableManifests.values()) + const loadableManifestPath = join(distDir, REACT_LOADABLE_MANIFEST) + const middlewareloadableManifestPath = join( + distDir, + 'server', + `${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js` + ) + + const json = JSON.stringify(loadableManifest, null, 2) + + deleteCache(loadableManifestPath) + deleteCache(middlewareloadableManifestPath) + await writeFileAtomic(loadableManifestPath, json) + await writeFileAtomic( + middlewareloadableManifestPath, + `self.__REACT_LOADABLE_MANIFEST=${JSON.stringify(json)}` + ) +} + +export async function writeManifests( + opts: SetupOpts, + distDir: string, + buildManifests: BuildManifests, + appBuildManifests: AppBuildManifests, + pagesManifests: PagesManifests, + appPathsManifests: AppPathsManifests, + middlewareManifests: MiddlewareManifests, + actionManifests: ActionManifests, + fontManifests: FontManifests, + loadableManifests: LoadableManifests, + currentEntrypoints: CurrentEntrypoints +): Promise { + await writeBuildManifest( + distDir, + buildManifests, + currentEntrypoints, + opts.fsChecker.rewrites + ) + await writeAppBuildManifest(distDir, appBuildManifests) + await writePagesManifest(distDir, pagesManifests) + await writeAppPathsManifest(distDir, appPathsManifests) + await writeMiddlewareManifest(distDir, middlewareManifests) + await writeActionManifest(distDir, actionManifests) + await writeFontManifest(distDir, fontManifests) + await writeLoadableManifest(distDir, loadableManifests) + await writeFallbackBuildManifest(distDir, buildManifests) +}