Skip to content

Commit

Permalink
Add client HMR tracing for debugging (#36328)
Browse files Browse the repository at this point in the history
This adds client HMR event tracing to help debug the client state for reproductions easier
  • Loading branch information
ijjk committed Apr 21, 2022
1 parent 5a93e21 commit b5d9ce5
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
35 changes: 33 additions & 2 deletions packages/next/client/dev/error-overlay/hot-dev-client.js
Expand Up @@ -34,7 +34,7 @@ import {
onFullRefreshNeeded,
} from 'next/dist/compiled/@next/react-dev-overlay/client'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { addMessageListener } from './websocket'
import { addMessageListener, sendMessage } from './websocket'
import formatWebpackMessages from './format-webpack-messages'

// This alternative WebpackDevServer combines the functionality of:
Expand All @@ -46,6 +46,8 @@ import formatWebpackMessages from './format-webpack-messages'
// that looks similar to our console output. The error overlay is inspired by:
// https://github.com/glenjamin/webpack-hot-middleware

window.__nextDevClientId = Math.round(Math.random() * 100 + Date.now())

let hadRuntimeError = false
let customHmrEventHandler
export default function connect() {
Expand Down Expand Up @@ -188,8 +190,17 @@ function onFastRefresh(hasUpdates) {
}

if (startLatency) {
const latency = Date.now() - startLatency
const endLatency = Date.now()
const latency = endLatency - startLatency
console.log(`[Fast Refresh] done in ${latency}ms`)
sendMessage(
JSON.stringify({
event: 'client-hmr-latency',
id: window.__nextDevClientId,
startTime: startLatency,
endTime: endLatency,
})
)
if (self.__NEXT_HMR_LATENCY_CB) {
self.__NEXT_HMR_LATENCY_CB(latency)
}
Expand Down Expand Up @@ -220,14 +231,34 @@ function processMessage(e) {
const { errors, warnings } = obj
const hasErrors = Boolean(errors && errors.length)
if (hasErrors) {
sendMessage(
JSON.stringify({
event: 'client-error',
errorCount: errors.length,
clientId: window.__nextDevClientId,
})
)
return handleErrors(errors)
}

const hasWarnings = Boolean(warnings && warnings.length)
if (hasWarnings) {
sendMessage(
JSON.stringify({
event: 'client-warning',
warningCount: warnings.length,
clientId: window.__nextDevClientId,
})
)
return handleWarnings(warnings)
}

sendMessage(
JSON.stringify({
event: 'client-success',
clientId: window.__nextDevClientId,
})
)
return handleSuccess()
}
default: {
Expand Down
21 changes: 21 additions & 0 deletions packages/next/client/dev/webpack-hot-middleware-client.js
@@ -1,15 +1,29 @@
import connect from './error-overlay/hot-dev-client'
import { sendMessage } from './error-overlay/websocket'

export default () => {
const devClient = connect()

devClient.subscribeToHmrEvent((obj) => {
if (obj.action === 'reloadPage') {
sendMessage(
JSON.stringify({
event: 'client-reload-page',
clientId: window.__nextDevClientId,
})
)
return window.location.reload()
}
if (obj.action === 'removedPage') {
const [page] = obj.data
if (page === window.next.router.pathname) {
sendMessage(
JSON.stringify({
event: 'client-removed-page',
clientId: window.__nextDevClientId,
page,
})
)
return window.location.reload()
}
return
Expand All @@ -20,6 +34,13 @@ export default () => {
page === window.next.router.pathname &&
typeof window.next.router.components[page] === 'undefined'
) {
sendMessage(
JSON.stringify({
event: 'client-added-page',
clientId: window.__nextDevClientId,
page,
})
)
return window.location.reload()
}
return
Expand Down
71 changes: 71 additions & 0 deletions packages/next/server/dev/hot-reloader.ts
Expand Up @@ -288,6 +288,77 @@ export default class HotReloader {
wsServer.handleUpgrade(req, req.socket, head, (client) => {
this.webpackHotMiddleware?.onHMR(client)
this.onDemandEntries?.onHMR(client)

client.addEventListener('message', ({ data }) => {
data = typeof data !== 'string' ? data.toString() : data

try {
const payload = JSON.parse(data)

let traceChild:
| {
name: string
startTime?: bigint
endTime?: bigint
attrs?: Record<string, number | string>
}
| undefined

switch (payload.event) {
case 'client-hmr-latency': {
traceChild = {
name: payload.event,
startTime: BigInt(payload.startTime * 1000 * 1000),
endTime: BigInt(payload.endTime * 1000 * 1000),
}
break
}
case 'client-reload-page':
case 'client-success': {
traceChild = {
name: payload.event,
}
break
}
case 'client-error': {
traceChild = {
name: payload.event,
attrs: { errorCount: payload.errorCount },
}
break
}
case 'client-warning': {
traceChild = {
name: payload.event,
attrs: { warningCount: payload.warningCount },
}
break
}
case 'client-removed-page':
case 'client-added-page': {
traceChild = {
name: payload.event,
attrs: { page: payload.page || '' },
}
break
}
default: {
break
}
}

if (traceChild) {
this.hotReloaderSpan.manualTraceChild(
traceChild.name,
traceChild.startTime || process.hrtime.bigint(),
traceChild.endTime || process.hrtime.bigint(),
{ ...traceChild.attrs, clientId: payload.id }
)
}
} catch (_) {
// invalid WebSocket message
}
})
})
}

Expand Down
7 changes: 7 additions & 0 deletions test/development/basic/hmr.test.ts
Expand Up @@ -766,4 +766,11 @@ describe('basic HMR', () => {
}
})
})

it('should have client HMR events in trace file', async () => {
const traceData = await next.readFile('.next/trace')
expect(traceData).toContain('client-hmr-latency')
expect(traceData).toContain('client-error')
expect(traceData).toContain('client-success')
})
})

0 comments on commit b5d9ce5

Please sign in to comment.