diff --git a/packages/next/src/build/create-compiler-aliases.ts b/packages/next/src/build/create-compiler-aliases.ts index ebc403c10a507..39d3a70863c18 100644 --- a/packages/next/src/build/create-compiler-aliases.ts +++ b/packages/next/src/build/create-compiler-aliases.ts @@ -19,6 +19,10 @@ import { } from './webpack-config' import { WEBPACK_LAYERS } from '../lib/constants' +interface CompilerAliases { + [alias: string]: string | string[] +} + export function createWebpackAliases({ dev, pageExtensions, @@ -47,16 +51,9 @@ export function createWebpackAliases({ isNodeServer: boolean clientResolveRewrites: string hasRewrites: boolean -}): - | { - alias: string | false | string[] - name: string - onlyModule?: boolean | undefined - }[] - | { [index: string]: string | false | string[] } - | undefined { - const customAppAliases: { [key: string]: string[] } = {} - const customDocumentAliases: { [key: string]: string[] } = {} +}): CompilerAliases { + const customAppAliases: CompilerAliases = {} + const customDocumentAliases: CompilerAliases = {} if (dev) { const nextDistPath = 'next/dist/' + (isEdgeServer ? 'esm/' : '') @@ -211,9 +208,9 @@ export function createWebpackAliases({ } } -export function createServerOnlyClientOnlyAliases(isServer: boolean): { - [aliasPath: string]: string -} { +export function createServerOnlyClientOnlyAliases( + isServer: boolean +): CompilerAliases { return isServer ? { 'server-only$': 'next/dist/compiled/server-only/empty', @@ -244,7 +241,7 @@ export function createRSCAliases( isEdgeServer: boolean reactProductionProfiling: boolean } -) { +): CompilerAliases { let alias: Record = { react$: `next/dist/compiled/react${bundledReactChannel}`, 'react-dom$': `next/dist/compiled/react-dom${bundledReactChannel}`, @@ -309,7 +306,7 @@ export function createRSCAliases( // Insert aliases for Next.js stubs of fetch, object-assign, and url // Keep in sync with insert_optimized_module_aliases in import_map.rs -export function getOptimizedModuleAliases(): { [pkg: string]: string } { +export function getOptimizedModuleAliases(): CompilerAliases { return { unfetch: require.resolve('next/dist/build/polyfills/fetch/index.js'), 'isomorphic-unfetch': require.resolve( @@ -338,7 +335,7 @@ export function getOptimizedModuleAliases(): { [pkg: string]: string } { } // Alias these modules to be resolved with "module" if possible. -function getBarrelOptimizationAliases(packages: string[]) { +function getBarrelOptimizationAliases(packages: string[]): CompilerAliases { const aliases: { [pkg: string]: string } = {} const mainFields = ['module', 'main'] @@ -361,12 +358,12 @@ function getBarrelOptimizationAliases(packages: string[]) { return aliases } -function getReactProfilingInProduction() { +function getReactProfilingInProduction(): CompilerAliases { return { 'react-dom$': 'react-dom/profiling', } } -export function createServerComponentsNoopAliases() { +export function createServerComponentsNoopAliases(): CompilerAliases { return { [require.resolve('next/head')]: require.resolve( 'next/dist/client/components/noop-head' diff --git a/packages/next/src/client/dev/dev-build-watcher.ts b/packages/next/src/client/dev/dev-build-watcher.ts index 9566ab78f51fd..55514a9c8aff1 100644 --- a/packages/next/src/client/dev/dev-build-watcher.ts +++ b/packages/next/src/client/dev/dev-build-watcher.ts @@ -95,6 +95,7 @@ export default function initializeBuildWatcher( break case HMR_ACTIONS_SENT_TO_BROWSER.BUILT: case HMR_ACTIONS_SENT_TO_BROWSER.SYNC: + case HMR_ACTIONS_SENT_TO_BROWSER.FINISH_BUILDING: hide() break } diff --git a/packages/next/src/server/dev/hot-reloader-types.ts b/packages/next/src/server/dev/hot-reloader-types.ts index 922dc95452e0d..65385b4ba5a30 100644 --- a/packages/next/src/server/dev/hot-reloader-types.ts +++ b/packages/next/src/server/dev/hot-reloader-types.ts @@ -17,6 +17,7 @@ export const enum HMR_ACTIONS_SENT_TO_BROWSER { SYNC = 'sync', BUILT = 'built', BUILDING = 'building', + FINISH_BUILDING = 'finishBuilding', DEV_PAGES_MANIFEST_UPDATE = 'devPagesManifestUpdate', TURBOPACK_MESSAGE = 'turbopack-message', SERVER_ERROR = 'serverError', @@ -37,6 +38,10 @@ interface BuildingAction { action: HMR_ACTIONS_SENT_TO_BROWSER.BUILDING } +interface FinishBuildingAction { + action: HMR_ACTIONS_SENT_TO_BROWSER.FINISH_BUILDING +} + interface SyncAction { action: HMR_ACTIONS_SENT_TO_BROWSER.SYNC hash: string @@ -95,6 +100,7 @@ export type HMR_ACTION_TYPES = | TurbopackMessageAction | TurbopackConnectedAction | BuildingAction + | FinishBuildingAction | SyncAction | BuiltAction | AddedPageAction diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 948a7c32f1ed1..a2e4db5652434 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -373,10 +373,22 @@ async function startWatcher(opts: SetupOpts) { // We ignore source maps if (p.endsWith('.map')) continue let key = `${id}:${p}` - const previousHash = serverPathState.get(key) - if (previousHash !== contentHash) { + const localHash = serverPathState.get(key) + const globaHash = serverPathState.get(p) + if ( + (localHash && localHash !== contentHash) || + (globaHash && globaHash !== contentHash) + ) { hasChange = true serverPathState.set(key, contentHash) + serverPathState.set(p, contentHash) + } else { + if (!localHash) { + serverPathState.set(key, contentHash) + } + if (!globaHash) { + serverPathState.set(p, contentHash) + } } } @@ -404,6 +416,45 @@ async function startWatcher(opts: SetupOpts) { return result } + const buildingIds = new Set() + const readyIds = new Set() + function startBuilding(id: string, forceRebuild: boolean = false) { + if (!forceRebuild && readyIds.has(id)) { + return () => {} + } + if (buildingIds.size === 0) { + consoleStore.setState( + { + loading: true, + trigger: id, + } as OutputState, + true + ) + hotReloader.send({ + action: HMR_ACTIONS_SENT_TO_BROWSER.BUILDING, + }) + } + buildingIds.add(id) + return function finishBuilding() { + if (buildingIds.size === 0) { + return + } + readyIds.add(id) + buildingIds.delete(id) + if (buildingIds.size === 0) { + hotReloader.send({ + action: HMR_ACTIONS_SENT_TO_BROWSER.FINISH_BUILDING, + }) + consoleStore.setState( + { + loading: false, + } as OutputState, + true + ) + } + } + } + let hmrHash = 0 const sendHmrDebounce = debounce(() => { interface HmrError { @@ -581,8 +632,6 @@ async function startWatcher(opts: SetupOpts) { ) } - const buildingReported = new Set() - async function changeSubscription( page: string, endpoint: Endpoint | undefined, @@ -598,14 +647,6 @@ async function startWatcher(opts: SetupOpts) { const changed = await changedPromise for await (const change of changed) { - consoleStore.setState( - { - loading: true, - trigger: page, - } as OutputState, - true - ) - processIssues(page, page, change) const payload = await makePayload(page, change) if (payload) sendHmr('endpoint-change', page, payload) @@ -1029,6 +1070,7 @@ async function startWatcher(opts: SetupOpts) { await processMiddleware() changeSubscription('middleware', middleware.endpoint, async () => { + const finishBuilding = startBuilding('middleware', true) await processMiddleware() await propagateServerField( 'actualMiddlewareFile', @@ -1037,7 +1079,7 @@ async function startWatcher(opts: SetupOpts) { await propagateServerField('middleware', serverFields.middleware) await writeMiddlewareManifest() - console.log('middleware changes') + finishBuilding() return { event: HMR_ACTIONS_SENT_TO_BROWSER.MIDDLEWARE_CHANGES } }) prevMiddleware = true @@ -1223,47 +1265,50 @@ async function startWatcher(opts: SetupOpts) { let page = definition?.pathname ?? inputPage if (page === '/_error') { - if (globalEntries.app) { - const writtenEndpoint = await processResult( - '_app', - await globalEntries.app.writeToDisk() - ) - processIssues('_app', '_app', writtenEndpoint) - } - await loadBuildManifest('_app') - await loadPagesManifest('_app') - - if (globalEntries.document) { - const writtenEndpoint = await processResult( - '_document', - await globalEntries.document.writeToDisk() - ) - changeSubscription('_document', globalEntries.document, () => { - return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } - }) - processIssues('_document', '_document', writtenEndpoint) - } - await loadPagesManifest('_document') + let finishBuilding = startBuilding(page) + try { + if (globalEntries.app) { + const writtenEndpoint = await processResult( + '_app', + await globalEntries.app.writeToDisk() + ) + processIssues('_app', '_app', writtenEndpoint) + } + await loadBuildManifest('_app') + await loadPagesManifest('_app') - if (globalEntries.error) { - const writtenEndpoint = await processResult( - '_error', - await globalEntries.error.writeToDisk() - ) - processIssues(page, page, writtenEndpoint) - } - await loadBuildManifest('_error') - await loadPagesManifest('_error') + if (globalEntries.document) { + const writtenEndpoint = await processResult( + '_document', + await globalEntries.document.writeToDisk() + ) + changeSubscription('_document', globalEntries.document, () => { + return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } + }) + processIssues('_document', '_document', writtenEndpoint) + } + await loadPagesManifest('_document') - await writeBuildManifest(opts.fsChecker.rewrites) - await writeFallbackBuildManifest() - await writePagesManifest() - await writeMiddlewareManifest() - await writeLoadableManifest() + if (globalEntries.error) { + const writtenEndpoint = await processResult( + '_error', + await globalEntries.error.writeToDisk() + ) + processIssues(page, page, writtenEndpoint) + } + await loadBuildManifest('_error') + await loadPagesManifest('_error') + await writeBuildManifest(opts.fsChecker.rewrites) + await writeFallbackBuildManifest() + await writePagesManifest() + await writeMiddlewareManifest() + await writeLoadableManifest() + } finally { + finishBuilding() + } return } - await currentEntriesHandling const route = curEntries.get(page) ?? @@ -1282,202 +1327,201 @@ async function startWatcher(opts: SetupOpts) { throw new PageNotFoundError(`route not found ${page}`) } - if (!buildingReported.has(page)) { - buildingReported.add(page) - let suffix + let suffix + switch (route.type) { + case 'app-page': + suffix = 'page' + break + case 'app-route': + suffix = 'route' + break + case 'page': + case 'page-api': + suffix = '' + break + default: + throw new Error('Unexpected route type ' + route.type) + } + + const buildingKey = `${page}${ + !page.endsWith('/') && suffix.length > 0 ? '/' : '' + }${suffix}` + let finishBuilding: (() => void) | undefined = undefined + + try { switch (route.type) { - case 'app-page': - suffix = 'page' - break - case 'app-route': - suffix = 'route' - break - case 'page': - case 'page-api': - suffix = '' - break - default: - throw new Error('Unexpected route type ' + route.type) - } + case 'page': { + if (isApp) { + throw new Error( + `mis-matched route type: isApp && page for ${page}` + ) + } - consoleStore.setState( - { - loading: true, - trigger: `${page}${ - !page.endsWith('/') && suffix.length > 0 ? '/' : '' - }${suffix}`, - } as OutputState, - true - ) - } + finishBuilding = startBuilding(buildingKey) + if (globalEntries.app) { + const writtenEndpoint = await processResult( + '_app', + await globalEntries.app.writeToDisk() + ) + processIssues('_app', '_app', writtenEndpoint) + } + await loadBuildManifest('_app') + await loadPagesManifest('_app') - switch (route.type) { - case 'page': { - if (isApp) { - throw new Error( - `mis-matched route type: isApp && page for ${page}` - ) - } + if (globalEntries.document) { + const writtenEndpoint = await processResult( + '_document', + await globalEntries.document.writeToDisk() + ) - if (globalEntries.app) { - const writtenEndpoint = await processResult( - '_app', - await globalEntries.app.writeToDisk() - ) - processIssues('_app', '_app', writtenEndpoint) - } - await loadBuildManifest('_app') - await loadPagesManifest('_app') + changeSubscription('_document', globalEntries.document, () => { + return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } + }) + processIssues('_document', '_document', writtenEndpoint) + } + await loadPagesManifest('_document') - if (globalEntries.document) { const writtenEndpoint = await processResult( - '_document', - await globalEntries.document.writeToDisk() + page, + await route.htmlEndpoint.writeToDisk() ) - changeSubscription('_document', globalEntries.document, () => { - return { action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE } - }) - processIssues('_document', '_document', writtenEndpoint) - } - await loadPagesManifest('_document') + changeSubscription( + page, + route.dataEndpoint, + (pageName, change) => { + switch (change.type) { + case ServerClientChangeType.Server: + case ServerClientChangeType.Both: + return { + event: HMR_ACTIONS_SENT_TO_BROWSER.SERVER_ONLY_CHANGES, + pages: [pageName], + } + default: + } + } + ) - const writtenEndpoint = await processResult( - page, - await route.htmlEndpoint.writeToDisk() - ) + const type = writtenEndpoint?.type - changeSubscription(page, route.dataEndpoint, (pageName, change) => { - switch (change.type) { - case ServerClientChangeType.Server: - case ServerClientChangeType.Both: - return { - event: HMR_ACTIONS_SENT_TO_BROWSER.SERVER_ONLY_CHANGES, - pages: [pageName], - } - default: + await loadBuildManifest(page) + await loadPagesManifest(page) + if (type === 'edge') { + await loadMiddlewareManifest(page, 'pages') + } else { + middlewareManifests.delete(page) } - }) + await loadLoadableManifest(page, 'pages') + + await writeBuildManifest(opts.fsChecker.rewrites) + await writeFallbackBuildManifest() + await writePagesManifest() + await writeMiddlewareManifest() + await writeLoadableManifest() - const type = writtenEndpoint?.type + processIssues(page, page, writtenEndpoint) - await loadBuildManifest(page) - await loadPagesManifest(page) - if (type === 'edge') { - await loadMiddlewareManifest(page, 'pages') - } else { - middlewareManifests.delete(page) + break } - await loadLoadableManifest(page, 'pages') + case 'page-api': { + // We don't throw on ensureOpts.isApp === true here + // since this can happen when app pages make + // api requests to page API routes. - await writeBuildManifest(opts.fsChecker.rewrites) - await writeFallbackBuildManifest() - await writePagesManifest() - await writeMiddlewareManifest() - await writeLoadableManifest() + finishBuilding = startBuilding(buildingKey) + const writtenEndpoint = await processResult( + page, + await route.endpoint.writeToDisk() + ) - processIssues(page, page, writtenEndpoint) + const type = writtenEndpoint?.type - break - } - case 'page-api': { - // We don't throw on ensureOpts.isApp === true here - // since this can happen when app pages make - // api requests to page API routes. + await loadPagesManifest(page) + if (type === 'edge') { + await loadMiddlewareManifest(page, 'pages') + } else { + middlewareManifests.delete(page) + } + await loadLoadableManifest(page, 'pages') - const writtenEndpoint = await processResult( - page, - await route.endpoint.writeToDisk() - ) + await writePagesManifest() + await writeMiddlewareManifest() + await writeLoadableManifest() - const type = writtenEndpoint?.type + processIssues(page, page, writtenEndpoint) - await loadPagesManifest(page) - if (type === 'edge') { - await loadMiddlewareManifest(page, 'pages') - } else { - middlewareManifests.delete(page) + break } - await loadLoadableManifest(page, 'pages') + case 'app-page': { + finishBuilding = startBuilding(buildingKey) + const writtenEndpoint = await processResult( + page, + await route.htmlEndpoint.writeToDisk() + ) - await writePagesManifest() - await writeMiddlewareManifest() - await writeLoadableManifest() + changeSubscription(page, route.rscEndpoint, (_page, change) => { + switch (change.type) { + case ServerClientChangeType.Server: + case ServerClientChangeType.Both: + return { + action: + HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES, + } + default: + } + }) - processIssues(page, page, writtenEndpoint) + await loadAppBuildManifest(page) + await loadBuildManifest(page, 'app') + await loadAppPathManifest(page, 'app') + await loadActionManifest(page) - break - } - case 'app-page': { - const writtenEndpoint = await processResult( - page, - await route.htmlEndpoint.writeToDisk() - ) + await writeAppBuildManifest() + await writeBuildManifest(opts.fsChecker.rewrites) + await writeAppPathsManifest() + await writeMiddlewareManifest() + await writeActionManifest() + await writeLoadableManifest() - changeSubscription(page, route.rscEndpoint, (_page, change) => { - switch (change.type) { - case ServerClientChangeType.Server: - case ServerClientChangeType.Both: - return { - action: - HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES, - } - default: - } - }) + processIssues(page, page, writtenEndpoint, true) - await loadAppBuildManifest(page) - await loadBuildManifest(page, 'app') - await loadAppPathManifest(page, 'app') - await loadActionManifest(page) + break + } + case 'app-route': { + finishBuilding = startBuilding(buildingKey) + const writtenEndpoint = await processResult( + page, + await route.endpoint.writeToDisk() + ) - await writeAppBuildManifest() - await writeBuildManifest(opts.fsChecker.rewrites) - await writeAppPathsManifest() - await writeMiddlewareManifest() - await writeActionManifest() - await writeLoadableManifest() + const type = writtenEndpoint?.type - processIssues(page, page, writtenEndpoint, true) + await loadAppPathManifest(page, 'app-route') + if (type === 'edge') { + await loadMiddlewareManifest(page, 'app-route') + } else { + middlewareManifests.delete(page) + } - break - } - case 'app-route': { - const writtenEndpoint = await processResult( - page, - await route.endpoint.writeToDisk() - ) + await writeAppBuildManifest() + await writeAppPathsManifest() + await writeMiddlewareManifest() + await writeMiddlewareManifest() + await writeLoadableManifest() - const type = writtenEndpoint?.type + processIssues(page, page, writtenEndpoint, true) - await loadAppPathManifest(page, 'app-route') - if (type === 'edge') { - await loadMiddlewareManifest(page, 'app-route') - } else { - middlewareManifests.delete(page) + break + } + default: { + throw new Error( + `unknown route type ${(route as any).type} for ${page}` + ) } - - await writeAppBuildManifest() - await writeAppPathsManifest() - await writeMiddlewareManifest() - await writeMiddlewareManifest() - await writeLoadableManifest() - - processIssues(page, page, writtenEndpoint, true) - - break - } - default: { - throw new Error(`unknown route type ${route.type} for ${page}`) } + } finally { + if (finishBuilding) finishBuilding() } - - consoleStore.setState( - { - loading: false, - } as OutputState, - true - ) }, } diff --git a/test/integration/build-indicator/test/index.test.js b/test/integration/build-indicator/test/index.test.js index f59be4a296e53..886d34beb1478 100644 --- a/test/integration/build-indicator/test/index.test.js +++ b/test/integration/build-indicator/test/index.test.js @@ -20,7 +20,7 @@ const installCheckVisible = (browser) => { watcherDiv.querySelector('div').className.indexOf('visible') > -1 ) if (window.showedBuilder) clearInterval(window.checkInterval) - }, 50) + }, 5) })()`) }