Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: generic dev build indicator #74615

Merged
merged 1 commit into from
Jan 7, 2025
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
@@ -0,0 +1,33 @@
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../server/dev/hot-reloader-types'
import { addMessageListener } from '../../components/react-dev-overlay/pages/websocket'
import { devBuildIndicator } from './internal/dev-build-indicator'

/** Integrates the generic dev build indicator with the Pages Router. */
export const initializeDevBuildIndicatorForPageRouter = () => {
if (!process.env.__NEXT_BUILD_INDICATOR) {
return
}

devBuildIndicator.initialize(process.env.__NEXT_BUILD_INDICATOR_POSITION)

// Add message listener specifically for Pages Router to handle lifecycle events
// related to dev builds (building, built, sync)
addMessageListener((obj) => {
try {
if (!('action' in obj)) {
return
}

// eslint-disable-next-line default-case
switch (obj.action) {
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING:
devBuildIndicator.show()
break
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
case HMR_ACTIONS_SENT_TO_BROWSER.SYNC:
devBuildIndicator.hide()
break
}
} catch {}
})
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../server/dev/hot-reloader-types'
import type { HMR_ACTION_TYPES } from '../../server/dev/hot-reloader-types'
import { addMessageListener } from '../components/react-dev-overlay/pages/websocket'

type VerticalPosition = 'top' | 'bottom'
type HorizonalPosition = 'left' | 'right'

export interface ShowHideHandler {
show: () => void
hide: () => void
const NOOP = () => {}

export const devBuildIndicator = {
/** Shows build indicator when Next.js is compiling. Requires initialize() first. */
show: NOOP,
/** Hides build indicator when Next.js finishes compiling. Requires initialize() first. */
hide: NOOP,
/** Sets up the build indicator UI component. Call this before using show/hide. */
initialize,
}

export default function initializeBuildWatcher(
toggleCallback: (handlers: ShowHideHandler) => void,
position = 'bottom-right'
) {
function initialize(position = 'bottom-right') {
const shadowHost = document.createElement('div')
const [verticalProperty, horizontalProperty] = position.split('-', 2) as [
VerticalPosition,
HorizonalPosition,
]
shadowHost.id = '__next-build-watcher'
shadowHost.id = '__next-build-indicator'
// Make sure container is fixed and on a high zIndex so it shows
shadowHost.style.position = 'fixed'
// Ensure container's position to be top or bottom (default)
Expand All @@ -42,7 +40,7 @@ export default function initializeBuildWatcher(
// the Shadow DOM, we need to prefix all the names so there
// will be no conflicts
shadowRoot = shadowHost
prefix = '__next-build-watcher-'
prefix = '__next-build-indicator-'
}

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

// Handle events

addMessageListener((obj) => {
try {
handleMessage(obj)
} catch {}
})

function show() {
devBuildIndicator.show = () => {
timeoutId && clearTimeout(timeoutId)
isVisible = true
isBuilding = true
updateContainer()
}

function hide() {
devBuildIndicator.hide = () => {
isBuilding = false
// Wait for the fade out transition to complete
timeoutId = setTimeout(() => {
Expand All @@ -83,28 +73,6 @@ export default function initializeBuildWatcher(
updateContainer()
}

function handleMessage(obj: HMR_ACTION_TYPES) {
if (!('action' in obj)) {
return
}

// eslint-disable-next-line default-case
switch (obj.action) {
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING:
show()
break
case HMR_ACTIONS_SENT_TO_BROWSER.BUILT:
case HMR_ACTIONS_SENT_TO_BROWSER.SYNC:
hide()
break
}
}

toggleCallback({
show,
hide,
})

function updateContainer() {
if (isBuilding) {
container.classList.add(`${prefix}building`)
Expand Down
18 changes: 5 additions & 13 deletions packages/next/src/client/page-bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { hydrate, router } from './'
import initOnDemandEntries from './dev/on-demand-entries-client'
import initializeBuildWatcher from './dev/dev-build-watcher'
import type { ShowHideHandler } from './dev/dev-build-watcher'
import { devBuildIndicator } from './dev/dev-build-indicator/internal/dev-build-indicator'
import { displayContent } from './dev/fouc'
import {
connectHMR,
Expand All @@ -15,20 +14,15 @@ import { HMR_ACTIONS_SENT_TO_BROWSER } from '../server/dev/hot-reloader-types'
import { RuntimeErrorHandler } from './components/react-dev-overlay/internal/helpers/runtime-error-handler'
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from './components/react-dev-overlay/shared'
import { performFullReload } from './components/react-dev-overlay/pages/hot-reloader-client'
import { initializeDevBuildIndicatorForPageRouter } from './dev/dev-build-indicator/initialize-for-page-router'

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

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

let buildIndicatorHandler: ShowHideHandler | undefined

if (process.env.__NEXT_BUILD_INDICATOR) {
initializeBuildWatcher((handler) => {
buildIndicatorHandler = handler
}, process.env.__NEXT_BUILD_INDICATOR_POSITION)
}
initializeDevBuildIndicatorForPageRouter()

let reloading = false

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

if (!router.clc && pages.includes(router.pathname)) {
console.log('Refreshing page data due to server-side change')

buildIndicatorHandler?.show()

const clearIndicator = () => buildIndicatorHandler?.hide()
devBuildIndicator.show()
const clearIndicator = () => devBuildIndicator.hide()

router
.replace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { NextInstance } from 'e2e-utils'
const installCheckVisible = (browser) => {
return browser.eval(`(function() {
window.checkInterval = setInterval(function() {
let watcherDiv = document.querySelector('#__next-build-watcher')
let watcherDiv = document.querySelector('#__next-build-indicator')
watcherDiv = watcherDiv.shadowRoot || watcherDiv
window.showedBuilder = window.showedBuilder || (
watcherDiv.querySelector('div').className.indexOf('visible') > -1
Expand Down
6 changes: 3 additions & 3 deletions test/integration/build-indicator/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let app
const installCheckVisible = (browser) => {
return browser.eval(`(function() {
window.checkInterval = setInterval(function() {
let watcherDiv = document.querySelector('#__next-build-watcher')
let watcherDiv = document.querySelector('#__next-build-indicator')
watcherDiv = watcherDiv.shadowRoot || watcherDiv
window.showedBuilder = window.showedBuilder || (
watcherDiv.querySelector('div').className.indexOf('visible') > -1
Expand Down Expand Up @@ -70,7 +70,7 @@ describe('Build Activity Indicator', () => {
it('Adds the build indicator container', async () => {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.body.innerHTML')
expect(html).toMatch(/__next-build-watcher/)
expect(html).toMatch(/__next-build-indicator/)
await browser.close()
})
;(process.env.TURBOPACK ? describe.skip : describe)('webpack only', () => {
Expand Down Expand Up @@ -120,7 +120,7 @@ describe('Build Activity Indicator', () => {
it('Does not add the build indicator container', async () => {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.body.innerHTML')
expect(html).not.toMatch(/__next-build-watcher/)
expect(html).not.toMatch(/__next-build-indicator/)
await browser.close()
})
})
Expand Down
Loading