Skip to content

Commit

Permalink
Merge branch 'canary' into fix-67641
Browse files Browse the repository at this point in the history
  • Loading branch information
Luluno01 authored Jul 10, 2024
2 parents f3a9d02 + 8cc2dd0 commit 4f668cf
Show file tree
Hide file tree
Showing 31 changed files with 679 additions and 184 deletions.
10 changes: 10 additions & 0 deletions docs/02-app/02-api-reference/05-next-config-js/devIndicators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ module.exports = {
}
```

There is also an indicator to show whether a page will be prerendered during a build in dev. It can be hidden per-page by clicking although if you never want it to show it can be disabled:

```js filename="next.config.js"
module.exports = {
devIndicators: {
appIsrStatus: false,
},
}
```

</AppOnly>

<PagesOnly>
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/build/webpack/plugins/define-env-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ export function getDefineEnv({
? 'nodejs'
: '',
'process.env.NEXT_MINIMAL': '',
'process.env.__NEXT_APP_ISR_INDICATOR': Boolean(
config.devIndicators.appIsrStatus
),
'process.env.__NEXT_PPR': checkIsAppPPREnabled(config.experimental.ppr),
'process.env.__NEXT_AFTER': config.experimental.after ?? false,
'process.env.NEXT_DEPLOYMENT_ID': config.deploymentId || false,
Expand Down
50 changes: 25 additions & 25 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,43 @@ import { createFromReadableStream } from 'react-server-dom-webpack/client'
import { HeadManagerContext } from '../shared/lib/head-manager-context.shared-runtime'
import { onRecoverableError } from './on-recoverable-error'
import { callServer } from './app-call-server'
import { isNextRouterError } from './components/is-next-router-error'
import {
ActionQueueContext,
createMutableActionQueue,
} from '../shared/lib/router/action-queue'
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../server/dev/hot-reloader-types'
import { isNextRouterError } from './components/is-next-router-error'
import { handleClientError } from './components/react-dev-overlay/internal/helpers/use-error-handler'

// Since React doesn't call onerror for errors caught in error boundaries.
// Patch console.error to collect information about hydration errors
const origConsoleError = window.console.error
window.console.error = (...args) => {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
if (
process.env.NODE_ENV !== 'production'
? isNextRouterError(args[1])
: isNextRouterError(args[0])
) {
return
const error = process.env.NODE_ENV !== 'production' ? args[1] : args[0]
if (!isNextRouterError(error)) {
if (process.env.NODE_ENV !== 'production') {
const storeHydrationErrorStateFromConsoleArgs =
require('./components/react-dev-overlay/internal/helpers/hydration-error-info')
.storeHydrationErrorStateFromConsoleArgs as typeof import('./components/react-dev-overlay/internal/helpers/hydration-error-info').storeHydrationErrorStateFromConsoleArgs
storeHydrationErrorStateFromConsoleArgs()

storeHydrationErrorStateFromConsoleArgs(...args)
handleClientError(error)
}

origConsoleError.apply(window.console, args)
}
origConsoleError.apply(window.console, args)
}

window.addEventListener('error', (ev: WindowEventMap['error']): void => {
if (isNextRouterError(ev.error)) {
ev.preventDefault()
return
}
})
if (process.env.NODE_ENV === 'development') {
const initializePrerenderIndicator =
require('./components/prerender-indicator')
.default as typeof import('./components/prerender-indicator').default

initializePrerenderIndicator((handlers) => {
window.next.isrIndicatorHandlers = handlers
})
}

/// <reference types="react-dom/experimental" />

Expand Down Expand Up @@ -203,16 +213,6 @@ export function hydrate() {
const isError =
document.documentElement.id === '__next_error__' || hasMissingTags

if (process.env.NODE_ENV !== 'production') {
// Patch console.error to collect information about hydration errors
const patchConsoleError =
require('./components/react-dev-overlay/internal/helpers/hydration-error-info')
.patchConsoleError as typeof import('./components/react-dev-overlay/internal/helpers/hydration-error-info').patchConsoleError
if (!isError) {
patchConsoleError()
}
}

if (isError) {
if (process.env.NODE_ENV !== 'production') {
// if an error is thrown while rendering an RSC stream, this will catch it in dev
Expand Down
226 changes: 226 additions & 0 deletions packages/next/src/client/components/prerender-indicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import type { ShowHideHandler } from '../dev/dev-build-watcher'

export default function initializePrerenderIndicator(
toggleCallback: (handlers: ShowHideHandler) => void
) {
const shadowHost = document.createElement('div')
shadowHost.id = '__next-prerender-indicator'
// Make sure container is fixed and on a high zIndex so it shows
shadowHost.style.position = 'fixed'
shadowHost.style.bottom = '20px'
shadowHost.style.right = '10px'
shadowHost.style.width = '0'
shadowHost.style.height = '0'
shadowHost.style.zIndex = '99998'
shadowHost.style.transition = 'all 100ms ease'

document.body.appendChild(shadowHost)

let shadowRoot
let prefix = ''

if (shadowHost.attachShadow) {
shadowRoot = shadowHost.attachShadow({ mode: 'open' })
} else {
// If attachShadow is undefined then the browser does not support
// the Shadow DOM, we need to prefix all the names so there
// will be no conflicts
shadowRoot = shadowHost
prefix = '__next-prerender-indicator-'
}

// Container
const container = createContainer(prefix)
shadowRoot.appendChild(container)

// CSS
const css = createCss(prefix)
shadowRoot.appendChild(css)

const expandEl = container.querySelector('a')
const closeEl: HTMLDivElement | null = container.querySelector(
`#${prefix}close`
) as any

if (closeEl) {
closeEl.title = 'Click to disable indicator for one hour in this session'
}

// State
const dismissKey = '__NEXT_DISMISS_PRERENDER_INDICATOR'
const dismissUntil = parseInt(
window.localStorage.getItem(dismissKey) || '',
10
)
const dismissed = dismissUntil > new Date().getTime()

if (dismissed) return

let isVisible = false

function updateContainer() {
if (isVisible) {
container.classList.add(`${prefix}visible`)
} else {
container.classList.remove(`${prefix}visible`)
}
}
const expandedClass = `${prefix}expanded`
let toggleTimeout: ReturnType<typeof setTimeout>

const toggleExpand = (expand = true) => {
clearTimeout(toggleTimeout)

toggleTimeout = setTimeout(() => {
if (expand) {
expandEl?.classList.add(expandedClass)
if (closeEl) {
closeEl.style.display = 'flex'
}
} else {
expandEl?.classList.remove(expandedClass)
if (closeEl) {
closeEl.style.display = 'none'
}
}
}, 50)
}

closeEl?.addEventListener('click', () => {
const oneHourAway = new Date().getTime() + 1 * 60 * 60 * 1000
window.localStorage.setItem(dismissKey, oneHourAway + '')
isVisible = false
updateContainer()
})
closeEl?.addEventListener('mouseenter', () => toggleExpand())
closeEl?.addEventListener('mouseleave', () => toggleExpand(false))
expandEl?.addEventListener('mouseenter', () => toggleExpand())
expandEl?.addEventListener('mouseleave', () => toggleExpand(false))

toggleCallback({
show: () => {
isVisible = true
updateContainer()
},
hide: () => {
isVisible = false
updateContainer()
},
})
}

function createContainer(prefix: string) {
const container = document.createElement('div')
container.id = `${prefix}container`
container.innerHTML = `
<button id="${prefix}close" title="Hide indicator for session">
<span>×</span>
</button>
<a href="https://nextjs.org/docs/api-reference/next.config.js/devIndicators" target="_blank" rel="noreferrer">
<div id="${prefix}icon-wrapper">
<svg width="15" height="20" viewBox="0 0 60 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36 3L30.74 41H8L36 3Z" fill="black"/>
<path d="M25 77L30.26 39H53L25 77Z" fill="black"/>
<path d="M13.5 33.5L53 39L47.5 46.5L7 41.25L13.5 33.5Z" fill="black"/>
</svg>
Prerendered Page
</div>
</a>
`
return container
}

function createCss(prefix: string) {
const css = document.createElement('style')
css.textContent = `
#${prefix}container {
position: absolute;
display: none;
bottom: 10px;
right: 15px;
}
#${prefix}close {
top: -10px;
right: -10px;
border: none;
width: 18px;
height: 18px;
color: #333333;
font-size: 16px;
cursor: pointer;
display: none;
position: absolute;
background: #ffffff;
border-radius: 100%;
align-items: center;
flex-direction: column;
justify-content: center;
}
#${prefix}container a {
color: inherit;
text-decoration: none;
width: 15px;
height: 23px;
overflow: hidden;
border-radius: 3px;
background: #fff;
color: #000;
font: initial;
cursor: pointer;
letter-spacing: initial;
text-shadow: initial;
text-transform: initial;
visibility: initial;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 4px 2px;
align-items: center;
box-shadow: 0 11px 40px 0 rgba(0, 0, 0, 0.25), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
display: flex;
transition: opacity 0.1s ease, bottom 0.1s ease, width 0.3s ease;
animation: ${prefix}fade-in 0.1s ease-in-out;
}
#${prefix}icon-wrapper {
width: 140px;
height: 20px;
display: flex;
flex-shrink: 0;
align-items: center;
position: relative;
}
#${prefix}icon-wrapper svg {
flex-shrink: 0;
margin-right: 3px;
}
#${prefix}container a.${prefix}expanded {
width: 135px;
}
#${prefix}container.${prefix}visible {
display: flex;
bottom: 10px;
opacity: 1;
}
@keyframes ${prefix}fade-in {
from {
bottom: 0px;
opacity: 0;
}
to {
bottom: 10px;
opacity: 1;
}
}
`

return css
}
Loading

0 comments on commit 4f668cf

Please sign in to comment.