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 dev overlay into hot reloader #41231

Merged
merged 8 commits into from Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/next/build/webpack/loaders/next-app-loader.ts
Expand Up @@ -185,12 +185,6 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
export const AppRouter = require('next/dist/client/components/app-router.client.js').default
export const LayoutRouter = require('next/dist/client/components/layout-router.client.js').default
export const RenderFromTemplateContext = require('next/dist/client/components/render-from-template-context.client.js').default
export const HotReloader = ${
// Disable HotReloader component in production
this.mode === 'development'
? `require('next/dist/client/components/hot-reloader.client.js').default`
: 'null'
}

export const staticGenerationAsyncStorage = require('next/dist/client/components/static-generation-async-storage.js').staticGenerationAsyncStorage
export const requestAsyncStorage = require('next/dist/client/components/request-async-storage.js').requestAsyncStorage
Expand Down
43 changes: 16 additions & 27 deletions packages/next/client/components/app-router.client.tsx
@@ -1,6 +1,6 @@
'client'

import type { PropsWithChildren, ReactElement, ReactNode } from 'react'
import type { ReactNode } from 'react'
import React, { useEffect, useMemo, useCallback } from 'react'
import { createFromFetch } from 'next/dist/compiled/react-server-dom-webpack'
import {
Expand Down Expand Up @@ -35,6 +35,12 @@ function urlToUrlWithoutFlightMarker(url: string): URL {
return urlWithoutFlightParameters
}

const HotReloader: typeof import('./hot-reloader').default | null =
process.env.NODE_ENV === 'production'
? null
: (require('./hot-reloader')
.default as typeof import('./hot-reloader').default)

/**
* Fetch the flight data for the provided url. Takes in the current router state to decide what to render server-side.
*/
Expand Down Expand Up @@ -78,20 +84,6 @@ export async function fetchServerResponse(
return [flightData, canonicalUrl]
}

/**
* Renders development error overlay when NODE_ENV is development.
*/
function ErrorOverlay({ children }: PropsWithChildren<{}>): ReactElement {
if (process.env.NODE_ENV === 'production') {
return <>{children}</>
} else {
const {
ReactDevOverlay,
} = require('next/dist/compiled/@next/react-dev-overlay/dist/client')
return <ReactDevOverlay globalOverlay>{children}</ReactDevOverlay>
}
}

// Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode.
// TODO-APP: move this back into AppRouter
let initialParallelRoutes: CacheNode['parallelRoutes'] =
Expand All @@ -106,12 +98,12 @@ export default function AppRouter({
initialTree,
initialCanonicalUrl,
children,
hotReloader,
assetPrefix,
}: {
initialTree: FlightRouterState
initialCanonicalUrl: string
children: ReactNode
hotReloader?: ReactNode
assetPrefix: string
}) {
const initialState = useMemo(() => {
return {
Expand Down Expand Up @@ -361,16 +353,13 @@ export default function AppRouter({
url: canonicalUrl,
}}
>
<ErrorOverlay>
{
// ErrorOverlay intentionally only wraps the children of app-router.
cache.subTreeData
}
</ErrorOverlay>
{
// HotReloader uses the router tree and router.reload() in order to apply Server Component changes.
hotReloader
}
{HotReloader ? (
<HotReloader assetPrefix={assetPrefix}>
{cache.subTreeData}
</HotReloader>
) : (
cache.subTreeData
)}
</LayoutRouterContext.Provider>
</AppRouterContext.Provider>
</GlobalLayoutRouterContext.Provider>
Expand Down
@@ -1,6 +1,5 @@
'client'

import {
import type { ReactNode } from 'react'
import React, {
useCallback,
useContext,
useEffect,
Expand All @@ -15,6 +14,7 @@ import {
onBuildError,
onBuildOk,
onRefresh,
ReactDevOverlay,
} from 'next/dist/compiled/@next/react-dev-overlay/dist/client'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import formatWebpackMessages from '../dev/error-overlay/format-webpack-messages'
Expand Down Expand Up @@ -398,7 +398,13 @@ function processMessage(
}
}

export default function HotReload({ assetPrefix }: { assetPrefix: string }) {
export default function HotReload({
assetPrefix,
children,
}: {
assetPrefix: string
children?: ReactNode
}) {
const { tree } = useContext(GlobalLayoutRouterContext)
const router = useRouter()

Expand Down Expand Up @@ -429,7 +435,7 @@ export default function HotReload({ assetPrefix }: { assetPrefix: string }) {
}

const { hostname, port } = window.location
const protocol = getSocketProtocol(assetPrefix || '')
const protocol = getSocketProtocol(assetPrefix)
const normalizedAssetPrefix = assetPrefix.replace(/^\/+/, '')

let url = `${protocol}://${hostname}:${port}${
Expand Down Expand Up @@ -494,5 +500,6 @@ export default function HotReload({ assetPrefix }: { assetPrefix: string }) {

// return () => clearInterval(interval)
// })
return null

return <ReactDevOverlay globalOverlay>{children}</ReactDevOverlay>
}
9 changes: 1 addition & 8 deletions packages/next/server/app-render.tsx
Expand Up @@ -810,9 +810,6 @@ export async function renderToHTMLOrFlight(
ComponentMod.LayoutRouter as typeof import('../client/components/layout-router.client').default
const RenderFromTemplateContext =
ComponentMod.RenderFromTemplateContext as typeof import('../client/components/render-from-template-context.client').default
const HotReloader = ComponentMod.HotReloader as
| typeof import('../client/components/hot-reloader.client').default
| null

/**
* Server Context is specifically only available in Server Components.
Expand Down Expand Up @@ -1373,11 +1370,7 @@ export async function renderToHTMLOrFlight(

return (
<AppRouter
hotReloader={
HotReloader && (
<HotReloader assetPrefix={renderOpts.assetPrefix || ''} />
)
}
assetPrefix={renderOpts.assetPrefix || ''}
initialCanonicalUrl={initialCanonicalUrl}
initialTree={initialTree}
>
Expand Down
1 change: 1 addition & 0 deletions packages/next/taskfile.js
Expand Up @@ -1751,6 +1751,7 @@ export async function ncc_mini_css_extract_plugin(task, opts) {
})
.target('compiled/mini-css-extract-plugin')
}

// eslint-disable-next-line camelcase
externals['ua-parser-js'] = 'next/dist/compiled/ua-parser-js'
export async function ncc_ua_parser_js(task, opts) {
Expand Down
109 changes: 54 additions & 55 deletions packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
Expand Up @@ -60,69 +60,68 @@ const shouldPreventDisplay = (
return preventType.includes(errorType)
}

const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
children,
preventDisplay,
globalOverlay,
}: {
type ReactDevOverlayProps = {
children?: React.ReactNode
preventDisplay?: ErrorType[]
globalOverlay?: boolean
}) {
const [state, dispatch] = React.useReducer<
React.Reducer<OverlayState, Bus.BusEvent>
>(reducer, {
nextId: 1,
buildError: null,
errors: [],
})
}

React.useEffect(() => {
Bus.on(dispatch)
return function () {
Bus.off(dispatch)
}
}, [dispatch])
const ReactDevOverlay: React.FunctionComponent<ReactDevOverlayProps> =
function ReactDevOverlay({ children, preventDisplay, globalOverlay }) {
const [state, dispatch] = React.useReducer<
React.Reducer<OverlayState, Bus.BusEvent>
>(reducer, {
nextId: 1,
buildError: null,
errors: [],
})

React.useEffect(() => {
Bus.on(dispatch)
return function () {
Bus.off(dispatch)
}
}, [dispatch])

const onComponentError = React.useCallback(
(_error: Error, _componentStack: string | null) => {
// TODO: special handling
},
[]
)
const onComponentError = React.useCallback(
(_error: Error, _componentStack: string | null) => {
// TODO: special handling
},
[]
)

const hasBuildError = state.buildError != null
const hasRuntimeErrors = Boolean(state.errors.length)
const hasBuildError = state.buildError != null
const hasRuntimeErrors = Boolean(state.errors.length)

const isMounted = hasBuildError || hasRuntimeErrors
const isMounted = hasBuildError || hasRuntimeErrors

return (
<React.Fragment>
<ErrorBoundary
globalOverlay={globalOverlay}
isMounted={isMounted}
onError={onComponentError}
>
{children ?? null}
</ErrorBoundary>
{isMounted ? (
<ShadowPortal globalOverlay={globalOverlay}>
<CssReset />
<Base />
<ComponentStyles />
return (
<React.Fragment>
<ErrorBoundary
globalOverlay={globalOverlay}
isMounted={isMounted}
onError={onComponentError}
>
{children ?? null}
</ErrorBoundary>
{isMounted ? (
<ShadowPortal globalOverlay={globalOverlay}>
<CssReset />
<Base />
<ComponentStyles />

{shouldPreventDisplay(
hasBuildError ? 'build' : hasRuntimeErrors ? 'runtime' : null,
preventDisplay
) ? null : hasBuildError ? (
<BuildError message={state.buildError!} />
) : hasRuntimeErrors ? (
<Errors errors={state.errors} />
) : undefined}
</ShadowPortal>
) : undefined}
</React.Fragment>
)
}
{shouldPreventDisplay(
hasBuildError ? 'build' : hasRuntimeErrors ? 'runtime' : null,
preventDisplay
) ? null : hasBuildError ? (
<BuildError message={state.buildError!} />
) : hasRuntimeErrors ? (
<Errors errors={state.errors} />
) : undefined}
</ShadowPortal>
) : undefined}
</React.Fragment>
)
}

export default ReactDevOverlay
@@ -1,5 +1,5 @@
import dataUriToBuffer, { MimeBuffer } from 'data-uri-to-buffer'
import { RawSourceMap } from 'source-map'
import type { RawSourceMap } from 'source-map'
import { getSourceMapUrl } from './getSourceMapUrl'

export function getRawSourceMap(fileContents: string): RawSourceMap | null {
Expand Down
7 changes: 2 additions & 5 deletions packages/react-dev-overlay/src/middleware.ts
Expand Up @@ -2,11 +2,8 @@ import { codeFrameColumns } from '@babel/code-frame'
import { constants as FS, promises as fs } from 'fs'
import { IncomingMessage, ServerResponse } from 'http'
import path from 'path'
import {
NullableMappedPosition,
RawSourceMap,
SourceMapConsumer,
} from 'source-map'
import type { NullableMappedPosition, RawSourceMap } from 'source-map'
import { SourceMapConsumer } from 'source-map'
import { StackFrame } from 'stacktrace-parser'
import url from 'url'
// @ts-ignore
Expand Down
5 changes: 4 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.