Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ function processMessage(
sendMessage,
[...hmrUpdate.updatedModules],
hmrUpdate.startMsSinceEpoch,
hmrUpdate.endMsSinceEpoch
hmrUpdate.endMsSinceEpoch,
// suppress the `client-hmr-latency` event if the update was a no-op:
hmrUpdate.hasUpdates
)
}
dispatcher.onBuildOk()
Expand Down Expand Up @@ -301,8 +303,8 @@ function processMessage(
} else {
webpackStartMsSinceEpoch = Date.now()
setPendingHotUpdateWebpack()
console.log('[Fast Refresh] rebuilding')
}
console.log('[Fast Refresh] rebuilding')
break
}
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
Expand Down Expand Up @@ -385,21 +387,22 @@ function processMessage(
break
}
case HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE: {
turbopackHmr!.onTurbopackMessage(obj)
dispatcher.onBeforeRefresh()
processTurbopackMessage({
type: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE,
data: obj.data,
})
dispatcher.onRefresh()
if (RuntimeErrorHandler.hadRuntimeError) {
console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR)
performFullReload(null, sendMessage)
}
turbopackHmr!.onTurbopackMessage(obj)
dispatcher.onRefresh()
break
}
// TODO-APP: make server component change more granular
case HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES: {
turbopackHmr?.onServerComponentChanges()
sendMessage(
JSON.stringify({
event: 'server-component-reload-page',
Expand Down Expand Up @@ -433,6 +436,7 @@ function processMessage(
return
}
case HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE: {
turbopackHmr?.onReloadPage()
sendMessage(
JSON.stringify({
event: 'client-reload-page',
Expand All @@ -445,6 +449,7 @@ function processMessage(
}
case HMR_ACTIONS_SENT_TO_BROWSER.ADDED_PAGE:
case HMR_ACTIONS_SENT_TO_BROWSER.REMOVED_PAGE: {
turbopackHmr?.onPageAddRemove()
// TODO-APP: potentially only refresh if the currently viewed page was added/removed.
return router.hmrRefresh()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ function handleSuccess() {
sendMessage,
[...hmrUpdate.updatedModules],
hmrUpdate.startMsSinceEpoch,
hmrUpdate.endMsSinceEpoch
hmrUpdate.endMsSinceEpoch,
hmrUpdate.hasUpdates
)
}
onBuildOk()
Expand Down Expand Up @@ -275,8 +276,8 @@ function processMessage(obj: HMR_ACTION_TYPES) {
turbopackHmr!.onBuilding()
} else {
webpackStartMsSinceEpoch = Date.now()
console.log('[Fast Refresh] rebuilding')
}
console.log('[Fast Refresh] rebuilding')
break
}
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
Expand Down Expand Up @@ -323,6 +324,7 @@ function processMessage(obj: HMR_ACTION_TYPES) {
return handleSuccess()
}
case HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES: {
turbopackHmr?.onServerComponentChanges()
if (hasCompileErrors || RuntimeErrorHandler.hadRuntimeError) {
window.location.reload()
}
Expand All @@ -348,6 +350,7 @@ function processMessage(obj: HMR_ACTION_TYPES) {
break
}
case HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE: {
turbopackHmr!.onTurbopackMessage(obj)
onBeforeRefresh()
for (const listener of turbopackMessageListeners) {
listener({
Expand All @@ -360,7 +363,6 @@ function processMessage(obj: HMR_ACTION_TYPES) {
performFullReload(null)
}
onRefresh()
turbopackHmr!.onTurbopackMessage(obj)
break
}
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,29 @@ declare global {
}
}

/**
* Logs information about a completed HMR to the console, the server (via a
* `client-hmr-latency` event), and to `self.__NEXT_HMR_LATENCY_CB` (a debugging
* hook).
*
* @param hasUpdate Set this to `false` to avoid reporting the HMR event via a
* `client-hmr-latency` event or to `self.__NEXT_HMR_LATENCY_CB`. Used by
* turbopack when we must report a message to the browser console (because we
* already logged a "rebuilding" message), but it's not a real HMR, so we
* don't want to impact our telemetry.
*/
export default function reportHmrLatency(
sendMessage: (message: string) => void,
updatedModules: ReadonlyArray<string | number>,
startMsSinceEpoch: number,
endMsSinceEpoch: number
endMsSinceEpoch: number,
hasUpdate: boolean = true
) {
const latencyMs = endMsSinceEpoch - startMsSinceEpoch
console.log(`[Fast Refresh] done in ${latencyMs}ms`)
if (!hasUpdate) {
return
}
sendMessage(
JSON.stringify({
event: 'client-hmr-latency',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { TurbopackMessageAction } from '../../../../server/dev/hot-reloader-types'
import type { Update as TurbopackUpdate } from '../../../../build/swc/types'

declare global {
interface Window {
__NEXT_HMR_TURBOPACK_REPORT_NOISY_NOOP_EVENTS: boolean | undefined
}
}

// How long to wait before reporting the HMR start, used to suppress irrelevant
// `BUILDING` events. Does not impact reported latency.
const TURBOPACK_HMR_START_DELAY_MS = 100

interface HmrUpdate {
hasUpdates: boolean
updatedModules: Set<string>
startMsSinceEpoch: number
endMsSinceEpoch: number
Expand All @@ -11,36 +22,97 @@ export class TurbopackHmr {
#updatedModules: Set<string>
#startMsSinceEpoch: number | undefined
#lastUpdateMsSinceEpoch: number | undefined
#deferredReportHmrStartId: ReturnType<typeof setTimeout> | undefined

constructor() {
this.#updatedModules = new Set()
}

// HACK: Turbopack tends to generate a lot of irrelevant "BUILDING" actions,
// as it reports *any* compilation, including fully no-op/cached compilations
// and those unrelated to HMR. Fixing this would require significant
// architectural changes.
//
// Work around this by deferring any "rebuilding" message by 100ms. If we get
// a BUILT event within that threshold and nothing has changed, just suppress
// the message entirely.
#runDeferredReportHmrStart() {
if (this.#deferredReportHmrStartId != null) {
console.log('[Fast Refresh] rebuilding')
this.#cancelDeferredReportHmrStart()
}
}

#cancelDeferredReportHmrStart() {
clearTimeout(this.#deferredReportHmrStartId)
this.#deferredReportHmrStartId = undefined
}

onBuilding() {
this.#lastUpdateMsSinceEpoch = undefined
this.#cancelDeferredReportHmrStart()
this.#startMsSinceEpoch = Date.now()

// report the HMR start after a short delay
this.#deferredReportHmrStartId = setTimeout(
() => this.#runDeferredReportHmrStart(),
// debugging feature: don't defer/suppress noisy no-op HMR update messages
self.__NEXT_HMR_TURBOPACK_REPORT_NOISY_NOOP_EVENTS
? 0
: TURBOPACK_HMR_START_DELAY_MS
)
}

onTurbopackMessage(msg: TurbopackMessageAction) {
/** Helper for other `onEvent` methods. */
#onUpdate() {
this.#runDeferredReportHmrStart()
this.#lastUpdateMsSinceEpoch = Date.now()
}

onTurbopackMessage(msg: TurbopackMessageAction) {
this.#onUpdate()
const updatedModules = extractModulesFromTurbopackMessage(msg.data)
for (const module of updatedModules) {
this.#updatedModules.add(module)
}
}

onServerComponentChanges() {
this.#onUpdate()
}

onReloadPage() {
this.#onUpdate()
}

onPageAddRemove() {
this.#onUpdate()
}

/**
* @returns `null` if the caller should ignore the update entirely. Returns an
* object with `hasUpdates: false` if the caller should report the end of
* the HMR in the browser console, but the HMR was a no-op.
*/
onBuilt(): HmrUpdate | null {
// it's possible for `this.#startMsSinceEpoch` to not be set if this was the initial
// computation, just return null in this case.
if (this.#startMsSinceEpoch == null) {
// Check that we got *any* `TurbopackMessageAction`, even if
// `updatedModules` is empty (not everything gets recorded there).
//
// There's also a case where `onBuilt` gets called before `onBuilding`,
// which can happen during initial page load. Ignore that too!
const hasUpdates =
this.#lastUpdateMsSinceEpoch != null && this.#startMsSinceEpoch != null
if (!hasUpdates && this.#deferredReportHmrStartId != null) {
// suppress the update entirely
this.#cancelDeferredReportHmrStart()
return null
}
this.#runDeferredReportHmrStart()

const result = {
hasUpdates,
updatedModules: this.#updatedModules,
startMsSinceEpoch: this.#startMsSinceEpoch!,
// Turbopack has a debounce which causes every BUILT message to appear
// 30ms late. We don't want to include this latency in our reporting, so
// prefer to use the last TURBOPACK_MESSAGE time.
endMsSinceEpoch: this.#lastUpdateMsSinceEpoch ?? Date.now(),
}
this.#updatedModules = new Set()
Expand Down
Loading