| | |
| | |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | import type { webpack } from 'next/dist/compiled/webpack/webpack' |
| | import type ws from 'next/dist/compiled/ws' |
| | import type { DevToolsConfig } from '../../next-devtools/dev-overlay/shared' |
| | import { isMiddlewareFilename } from '../../build/utils' |
| | import type { VersionInfo } from './parse-version-info' |
| | import type { HmrMessageSentToBrowser } from './hot-reloader-types' |
| | import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types' |
| | import { devIndicatorServerState } from './dev-indicator-server-state' |
| | import { createBinaryHmrMessageData } from './messages' |
| | import type { NextConfigComplete } from '../config-shared' |
| |
|
| | function isMiddlewareStats(stats: webpack.Stats) { |
| | for (const key of stats.compilation.entrypoints.keys()) { |
| | if (isMiddlewareFilename(key)) { |
| | return true |
| | } |
| | } |
| |
|
| | return false |
| | } |
| |
|
| | function statsToJson(stats?: webpack.Stats | null) { |
| | if (!stats) return {} |
| | return stats.toJson({ |
| | all: false, |
| | errors: true, |
| | hash: true, |
| | warnings: true, |
| | }) |
| | } |
| |
|
| | function getStatsForSyncEvent( |
| | clientStats: { ts: number; stats: webpack.Stats } | null, |
| | serverStats: { ts: number; stats: webpack.Stats } | null |
| | ) { |
| | if (!clientStats) return serverStats?.stats |
| | if (!serverStats) return clientStats?.stats |
| |
|
| | |
| | |
| | |
| | if (serverStats.stats.hasErrors()) { |
| | return serverStats.stats |
| | } |
| |
|
| | |
| | return serverStats.ts > clientStats.ts ? serverStats.stats : clientStats.stats |
| | } |
| |
|
| | export class WebpackHotMiddleware { |
| | private clientsWithoutHtmlRequestId = new Set<ws>() |
| | private clientsByHtmlRequestId: Map<string, ws> = new Map() |
| | private closed = false |
| | private clientLatestStats: { ts: number; stats: webpack.Stats } | null = null |
| | private middlewareLatestStats: { ts: number; stats: webpack.Stats } | null = |
| | null |
| | private serverLatestStats: { ts: number; stats: webpack.Stats } | null = null |
| |
|
| | constructor( |
| | compilers: webpack.Compiler[], |
| | private versionInfo: VersionInfo, |
| | private devtoolsFrontendUrl: string | undefined, |
| | private config: NextConfigComplete, |
| | private devToolsConfig: DevToolsConfig |
| | ) { |
| | compilers[0].hooks.invalid.tap( |
| | 'webpack-hot-middleware', |
| | this.onClientInvalid |
| | ) |
| | compilers[0].hooks.done.tap('webpack-hot-middleware', this.onClientDone) |
| | compilers[1].hooks.invalid.tap( |
| | 'webpack-hot-middleware', |
| | this.onServerInvalid |
| | ) |
| | compilers[1].hooks.done.tap('webpack-hot-middleware', this.onServerDone) |
| | compilers[2].hooks.done.tap('webpack-hot-middleware', this.onEdgeServerDone) |
| | compilers[2].hooks.invalid.tap( |
| | 'webpack-hot-middleware', |
| | this.onEdgeServerInvalid |
| | ) |
| | } |
| |
|
| | onClientInvalid = () => { |
| | if (this.closed || this.serverLatestStats?.stats.hasErrors()) return |
| | this.publish({ |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.BUILDING, |
| | }) |
| | } |
| |
|
| | onClientDone = (statsResult: webpack.Stats) => { |
| | this.clientLatestStats = { ts: Date.now(), stats: statsResult } |
| | if (this.closed || this.serverLatestStats?.stats.hasErrors()) return |
| | this.publishStats(statsResult) |
| | } |
| |
|
| | onServerInvalid = () => { |
| | if (!this.serverLatestStats?.stats.hasErrors()) return |
| | this.serverLatestStats = null |
| | if (this.clientLatestStats?.stats) { |
| | this.publishStats(this.clientLatestStats.stats) |
| | } |
| | } |
| |
|
| | onServerDone = (statsResult: webpack.Stats) => { |
| | if (this.closed) return |
| | if (statsResult.hasErrors()) { |
| | this.serverLatestStats = { ts: Date.now(), stats: statsResult } |
| | this.publishStats(statsResult) |
| | } |
| | } |
| |
|
| | onEdgeServerInvalid = () => { |
| | if (!this.middlewareLatestStats?.stats.hasErrors()) return |
| | this.middlewareLatestStats = null |
| | if (this.clientLatestStats?.stats) { |
| | this.publishStats(this.clientLatestStats.stats) |
| | } |
| | } |
| |
|
| | onEdgeServerDone = (statsResult: webpack.Stats) => { |
| | if (this.closed) return |
| | if (!isMiddlewareStats(statsResult)) { |
| | this.onServerInvalid() |
| | this.onServerDone(statsResult) |
| | } |
| |
|
| | if (statsResult.hasErrors()) { |
| | this.middlewareLatestStats = { ts: Date.now(), stats: statsResult } |
| | this.publishStats(statsResult) |
| | } |
| | } |
| |
|
| | public updateDevToolsConfig(newConfig: DevToolsConfig): void { |
| | this.devToolsConfig = newConfig |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | onHMR = (client: ws, htmlRequestId: string | null) => { |
| | if (this.closed) return |
| |
|
| | if (htmlRequestId) { |
| | this.clientsByHtmlRequestId.set(htmlRequestId, client) |
| | } else { |
| | this.clientsWithoutHtmlRequestId.add(client) |
| | } |
| |
|
| | client.addEventListener('close', () => { |
| | if (htmlRequestId) { |
| | this.clientsByHtmlRequestId.delete(htmlRequestId) |
| | } else { |
| | this.clientsWithoutHtmlRequestId.delete(client) |
| | } |
| | }) |
| |
|
| | const syncStats = getStatsForSyncEvent( |
| | this.clientLatestStats, |
| | this.serverLatestStats |
| | ) |
| |
|
| | if (syncStats) { |
| | const stats = statsToJson(syncStats) |
| | const middlewareStats = statsToJson(this.middlewareLatestStats?.stats) |
| |
|
| | if (devIndicatorServerState.disabledUntil < Date.now()) { |
| | devIndicatorServerState.disabledUntil = 0 |
| | } |
| |
|
| | this.publish({ |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.SYNC, |
| | hash: stats.hash!, |
| | errors: [...(stats.errors || []), ...(middlewareStats.errors || [])], |
| | warnings: [ |
| | ...(stats.warnings || []), |
| | ...(middlewareStats.warnings || []), |
| | ], |
| | versionInfo: this.versionInfo, |
| | debug: { |
| | devtoolsFrontendUrl: this.devtoolsFrontendUrl, |
| | }, |
| | devIndicator: devIndicatorServerState, |
| | devToolsConfig: this.devToolsConfig, |
| | }) |
| | } |
| | } |
| |
|
| | publishStats = (statsResult: webpack.Stats) => { |
| | const stats = statsResult.toJson({ |
| | all: false, |
| | hash: true, |
| | warnings: true, |
| | errors: true, |
| | moduleTrace: true, |
| | }) |
| |
|
| | this.publish({ |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.BUILT, |
| | hash: stats.hash!, |
| | warnings: stats.warnings || [], |
| | errors: stats.errors || [], |
| | }) |
| | } |
| |
|
| | getClient = (htmlRequestId: string): ws | undefined => { |
| | return this.clientsByHtmlRequestId.get(htmlRequestId) |
| | } |
| |
|
| | publishToClient = (client: ws, message: HmrMessageSentToBrowser) => { |
| | if (this.closed) { |
| | return |
| | } |
| |
|
| | const data = |
| | typeof message.type === 'number' |
| | ? createBinaryHmrMessageData(message) |
| | : JSON.stringify(message) |
| |
|
| | client.send(data) |
| | } |
| |
|
| | publish = (message: HmrMessageSentToBrowser) => { |
| | if (this.closed) { |
| | return |
| | } |
| |
|
| | for (const wsClient of [ |
| | ...this.clientsWithoutHtmlRequestId, |
| | ...this.clientsByHtmlRequestId.values(), |
| | ]) { |
| | this.publishToClient(wsClient, message) |
| | } |
| | } |
| |
|
| | publishToLegacyClients = (message: HmrMessageSentToBrowser) => { |
| | if (this.closed) { |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | if (!this.config.cacheComponents) { |
| | for (const wsClient of this.clientsByHtmlRequestId.values()) { |
| | this.publishToClient(wsClient, message) |
| | } |
| | } |
| |
|
| | for (const wsClient of this.clientsWithoutHtmlRequestId) { |
| | this.publishToClient(wsClient, message) |
| | } |
| | } |
| |
|
| | close = () => { |
| | if (this.closed) { |
| | return |
| | } |
| |
|
| | |
| | |
| | this.closed = true |
| |
|
| | for (const wsClient of [ |
| | ...this.clientsWithoutHtmlRequestId, |
| | ...this.clientsByHtmlRequestId.values(), |
| | ]) { |
| | |
| | wsClient.terminate() |
| | } |
| |
|
| | this.clientsWithoutHtmlRequestId.clear() |
| | this.clientsByHtmlRequestId.clear() |
| | } |
| |
|
| | deleteClient = (client: ws, htmlRequestId: string | null) => { |
| | if (htmlRequestId) { |
| | this.clientsByHtmlRequestId.delete(htmlRequestId) |
| | } else { |
| | this.clientsWithoutHtmlRequestId.delete(client) |
| | } |
| | } |
| |
|
| | hasClients = () => { |
| | return ( |
| | this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size > |
| | 0 |
| | ) |
| | } |
| |
|
| | getClientCount = () => { |
| | return ( |
| | this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size |
| | ) |
| | } |
| | } |
| |
|