Skip to content

Commit 5c49eec

Browse files
authored
refactor: generic dev build indicator (vercel#74615)
- Decoupled pages router specific logic from the dev build indicator to extract a generic version - Created a pages router specific initialize function for dev build indicator to be called on pages router hydration - This prepares for upcoming work on the dev build indicator for App router - Covered by [e2e test](https://github.com/vercel/next.js/blob/jude/refactor-dev-build-indicator/test/integration/build-indicator/test/index.test.js)
1 parent 6784575 commit 5c49eec

File tree

5 files changed

+56
-63
lines changed

5 files changed

+56
-63
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../server/dev/hot-reloader-types'
2+
import { addMessageListener } from '../../components/react-dev-overlay/pages/websocket'
3+
import { devBuildIndicator } from './internal/dev-build-indicator'
4+
5+
/** Integrates the generic dev build indicator with the Pages Router. */
6+
export const initializeDevBuildIndicatorForPageRouter = () => {
7+
if (!process.env.__NEXT_BUILD_INDICATOR) {
8+
return
9+
}
10+
11+
devBuildIndicator.initialize(process.env.__NEXT_BUILD_INDICATOR_POSITION)
12+
13+
// Add message listener specifically for Pages Router to handle lifecycle events
14+
// related to dev builds (building, built, sync)
15+
addMessageListener((obj) => {
16+
try {
17+
if (!('action' in obj)) {
18+
return
19+
}
20+
21+
// eslint-disable-next-line default-case
22+
switch (obj.action) {
23+
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING:
24+
devBuildIndicator.show()
25+
break
26+
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
27+
case HMR_ACTIONS_SENT_TO_BROWSER.SYNC:
28+
devBuildIndicator.hide()
29+
break
30+
}
31+
} catch {}
32+
})
33+
}

packages/next/src/client/dev/dev-build-watcher.ts renamed to packages/next/src/client/dev/dev-build-indicator/internal/dev-build-indicator.ts

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1-
/* eslint-disable @typescript-eslint/no-use-before-define */
2-
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../server/dev/hot-reloader-types'
3-
import type { HMR_ACTION_TYPES } from '../../server/dev/hot-reloader-types'
4-
import { addMessageListener } from '../components/react-dev-overlay/pages/websocket'
5-
61
type VerticalPosition = 'top' | 'bottom'
72
type HorizonalPosition = 'left' | 'right'
83

9-
export interface ShowHideHandler {
10-
show: () => void
11-
hide: () => void
4+
const NOOP = () => {}
5+
6+
export const devBuildIndicator = {
7+
/** Shows build indicator when Next.js is compiling. Requires initialize() first. */
8+
show: NOOP,
9+
/** Hides build indicator when Next.js finishes compiling. Requires initialize() first. */
10+
hide: NOOP,
11+
/** Sets up the build indicator UI component. Call this before using show/hide. */
12+
initialize,
1213
}
1314

14-
export default function initializeBuildWatcher(
15-
toggleCallback: (handlers: ShowHideHandler) => void,
16-
position = 'bottom-right'
17-
) {
15+
function initialize(position = 'bottom-right') {
1816
const shadowHost = document.createElement('div')
1917
const [verticalProperty, horizontalProperty] = position.split('-', 2) as [
2018
VerticalPosition,
2119
HorizonalPosition,
2220
]
23-
shadowHost.id = '__next-build-watcher'
21+
shadowHost.id = '__next-build-indicator'
2422
// Make sure container is fixed and on a high zIndex so it shows
2523
shadowHost.style.position = 'fixed'
2624
// Ensure container's position to be top or bottom (default)
@@ -42,7 +40,7 @@ export default function initializeBuildWatcher(
4240
// the Shadow DOM, we need to prefix all the names so there
4341
// will be no conflicts
4442
shadowRoot = shadowHost
45-
prefix = '__next-build-watcher-'
43+
prefix = '__next-build-indicator-'
4644
}
4745

4846
// Container
@@ -58,22 +56,14 @@ export default function initializeBuildWatcher(
5856
let isBuilding = false
5957
let timeoutId: null | ReturnType<typeof setTimeout> = null
6058

61-
// Handle events
62-
63-
addMessageListener((obj) => {
64-
try {
65-
handleMessage(obj)
66-
} catch {}
67-
})
68-
69-
function show() {
59+
devBuildIndicator.show = () => {
7060
timeoutId && clearTimeout(timeoutId)
7161
isVisible = true
7262
isBuilding = true
7363
updateContainer()
7464
}
7565

76-
function hide() {
66+
devBuildIndicator.hide = () => {
7767
isBuilding = false
7868
// Wait for the fade out transition to complete
7969
timeoutId = setTimeout(() => {
@@ -83,28 +73,6 @@ export default function initializeBuildWatcher(
8373
updateContainer()
8474
}
8575

86-
function handleMessage(obj: HMR_ACTION_TYPES) {
87-
if (!('action' in obj)) {
88-
return
89-
}
90-
91-
// eslint-disable-next-line default-case
92-
switch (obj.action) {
93-
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING:
94-
show()
95-
break
96-
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
97-
case HMR_ACTIONS_SENT_TO_BROWSER.SYNC:
98-
hide()
99-
break
100-
}
101-
}
102-
103-
toggleCallback({
104-
show,
105-
hide,
106-
})
107-
10876
function updateContainer() {
10977
if (isBuilding) {
11078
container.classList.add(`${prefix}building`)

packages/next/src/client/page-bootstrap.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { hydrate, router } from './'
22
import initOnDemandEntries from './dev/on-demand-entries-client'
3-
import initializeBuildWatcher from './dev/dev-build-watcher'
4-
import type { ShowHideHandler } from './dev/dev-build-watcher'
3+
import { devBuildIndicator } from './dev/dev-build-indicator/internal/dev-build-indicator'
54
import { displayContent } from './dev/fouc'
65
import {
76
connectHMR,
@@ -15,20 +14,15 @@ import { HMR_ACTIONS_SENT_TO_BROWSER } from '../server/dev/hot-reloader-types'
1514
import { RuntimeErrorHandler } from './components/react-dev-overlay/internal/helpers/runtime-error-handler'
1615
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from './components/react-dev-overlay/shared'
1716
import { performFullReload } from './components/react-dev-overlay/pages/hot-reloader-client'
17+
import { initializeDevBuildIndicatorForPageRouter } from './dev/dev-build-indicator/initialize-for-page-router'
1818

1919
export function pageBootstrap(assetPrefix: string) {
2020
connectHMR({ assetPrefix, path: '/_next/webpack-hmr' })
2121

2222
return hydrate({ beforeRender: displayContent }).then(() => {
2323
initOnDemandEntries()
2424

25-
let buildIndicatorHandler: ShowHideHandler | undefined
26-
27-
if (process.env.__NEXT_BUILD_INDICATOR) {
28-
initializeBuildWatcher((handler) => {
29-
buildIndicatorHandler = handler
30-
}, process.env.__NEXT_BUILD_INDICATOR_POSITION)
31-
}
25+
initializeDevBuildIndicatorForPageRouter()
3226

3327
let reloading = false
3428

@@ -98,10 +92,8 @@ export function pageBootstrap(assetPrefix: string) {
9892

9993
if (!router.clc && pages.includes(router.pathname)) {
10094
console.log('Refreshing page data due to server-side change')
101-
102-
buildIndicatorHandler?.show()
103-
104-
const clearIndicator = () => buildIndicatorHandler?.hide()
95+
devBuildIndicator.show()
96+
const clearIndicator = () => devBuildIndicator.hide()
10597

10698
router
10799
.replace(

test/development/basic/gssp-ssr-change-reloading/test/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { NextInstance } from 'e2e-utils'
1414
const installCheckVisible = (browser) => {
1515
return browser.eval(`(function() {
1616
window.checkInterval = setInterval(function() {
17-
let watcherDiv = document.querySelector('#__next-build-watcher')
17+
let watcherDiv = document.querySelector('#__next-build-indicator')
1818
watcherDiv = watcherDiv.shadowRoot || watcherDiv
1919
window.showedBuilder = window.showedBuilder || (
2020
watcherDiv.querySelector('div').className.indexOf('visible') > -1

test/integration/build-indicator/test/index.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let app
1414
const installCheckVisible = (browser) => {
1515
return browser.eval(`(function() {
1616
window.checkInterval = setInterval(function() {
17-
let watcherDiv = document.querySelector('#__next-build-watcher')
17+
let watcherDiv = document.querySelector('#__next-build-indicator')
1818
watcherDiv = watcherDiv.shadowRoot || watcherDiv
1919
window.showedBuilder = window.showedBuilder || (
2020
watcherDiv.querySelector('div').className.indexOf('visible') > -1
@@ -70,7 +70,7 @@ describe('Build Activity Indicator', () => {
7070
it('Adds the build indicator container', async () => {
7171
const browser = await webdriver(appPort, '/')
7272
const html = await browser.eval('document.body.innerHTML')
73-
expect(html).toMatch(/__next-build-watcher/)
73+
expect(html).toMatch(/__next-build-indicator/)
7474
await browser.close()
7575
})
7676
;(process.env.TURBOPACK ? describe.skip : describe)('webpack only', () => {
@@ -120,7 +120,7 @@ describe('Build Activity Indicator', () => {
120120
it('Does not add the build indicator container', async () => {
121121
const browser = await webdriver(appPort, '/')
122122
const html = await browser.eval('document.body.innerHTML')
123-
expect(html).not.toMatch(/__next-build-watcher/)
123+
expect(html).not.toMatch(/__next-build-indicator/)
124124
await browser.close()
125125
})
126126
})

0 commit comments

Comments
 (0)