Skip to content

Commit

Permalink
Handle on-demand-entries and error overlay for server components (#38480
Browse files Browse the repository at this point in the history
)
  • Loading branch information
timneutkens committed Jul 10, 2022
1 parent d920a17 commit 2d98759
Show file tree
Hide file tree
Showing 11 changed files with 696 additions and 62 deletions.
31 changes: 23 additions & 8 deletions packages/next/build/webpack-config.ts
Expand Up @@ -571,14 +571,29 @@ export default async function getBaseWebpackConfig(
.replace(/\\/g, '/'),
...(config.experimental.appDir
? {
[CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT]:
`./` +
path
.relative(
dir,
path.join(NEXT_PROJECT_ROOT_DIST_CLIENT, 'app-next.js')
)
.replace(/\\/g, '/'),
[CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT]: dev
? [
require.resolve(
`next/dist/compiled/@next/react-refresh-utils/dist/runtime`
),
`./` +
path
.relative(
dir,
path.join(
NEXT_PROJECT_ROOT_DIST_CLIENT,
'app-next-dev.js'
)
)
.replace(/\\/g, '/'),
]
: `./` +
path
.relative(
dir,
path.join(NEXT_PROJECT_ROOT_DIST_CLIENT, 'app-next.js')
)
.replace(/\\/g, '/'),
}
: {}),
} as ClientEntries)
Expand Down
6 changes: 6 additions & 0 deletions packages/next/build/webpack/loaders/next-app-loader.ts
Expand Up @@ -140,6 +140,12 @@ 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 HotReloader = ${
// Disable HotReloader component in production
this.mode === 'development'
? `require('next/dist/client/components/hot-reloader.client.js').default`
: 'null'
}
export const hooksClientContext = require('next/dist/client/components/hooks-client-context.js')
export const __next_app_webpack_require__ = __webpack_require__
Expand Down
69 changes: 62 additions & 7 deletions packages/next/client/app-index.tsx
Expand Up @@ -8,6 +8,46 @@ import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-we

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

// Override chunk URL mapping in the webpack runtime
// https://github.com/webpack/webpack/blob/2738eebc7880835d88c727d364ad37f3ec557593/lib/RuntimeGlobals.js#L204

declare global {
const __webpack_require__: any
}

// eslint-disable-next-line no-undef
const getChunkScriptFilename = __webpack_require__.u
const chunkFilenameMap: any = {}

// eslint-disable-next-line no-undef
__webpack_require__.u = (chunkId: any) => {
return chunkFilenameMap[chunkId] || getChunkScriptFilename(chunkId)
}

// Ignore the module ID transform in client.
// eslint-disable-next-line no-undef
// @ts-expect-error TODO: fix type
self.__next_require__ = __webpack_require__

// eslint-disable-next-line no-undef
// @ts-expect-error TODO: fix type
self.__next_chunk_load__ = (chunk) => {
if (chunk.endsWith('.css')) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = '/_next/' + chunk
document.head.appendChild(link)
return Promise.resolve()
}

const [chunkId, chunkFileName] = chunk.split(':')
chunkFilenameMap[chunkId] = `static/chunks/${chunkFileName}.js`

// @ts-ignore
// eslint-disable-next-line no-undef
return __webpack_chunk_load__(chunkId)
}

export const version = process.env.__NEXT_VERSION

const appElement: HTMLElement | Document | null = document
Expand Down Expand Up @@ -119,7 +159,7 @@ function useInitialServerResponse(cacheKey: string) {
return newResponse
}

const ServerRoot = ({ cacheKey }: { cacheKey: string }) => {
function ServerRoot({ cacheKey }: { cacheKey: string }) {
React.useEffect(() => {
rscCache.delete(cacheKey)
})
Expand All @@ -128,6 +168,19 @@ const ServerRoot = ({ cacheKey }: { cacheKey: string }) => {
return root
}

function ErrorOverlay({
children,
}: React.PropsWithChildren<{}>): React.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>
}
}

function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement {
if (process.env.__NEXT_TEST_MODE) {
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand All @@ -143,17 +196,19 @@ function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement {
return children as React.ReactElement
}

const RSCComponent = () => {
function RSCComponent() {
const cacheKey = getCacheKey()
return <ServerRoot cacheKey={cacheKey} />
}

export function hydrate() {
renderReactElement(appElement!, () => (
<React.StrictMode>
<Root>
<RSCComponent />
</Root>
</React.StrictMode>
<ErrorOverlay>
<React.StrictMode>
<Root>
<RSCComponent />
</Root>
</React.StrictMode>
</ErrorOverlay>
))
}
14 changes: 14 additions & 0 deletions packages/next/client/app-next-dev.js
@@ -0,0 +1,14 @@
import { hydrate, version } from './app-index'

// TODO: implement FOUC guard

// TODO: hydration warning

window.next = {
version,
appDir: true,
}

hydrate()

// TODO: build indicator
36 changes: 1 addition & 35 deletions packages/next/client/app-next.js
Expand Up @@ -6,41 +6,7 @@ import 'next/dist/client/components/layout-router.client.js'

window.next = {
version,
root: true,
}

// Override chunk URL mapping in the webpack runtime
// https://github.com/webpack/webpack/blob/2738eebc7880835d88c727d364ad37f3ec557593/lib/RuntimeGlobals.js#L204

// eslint-disable-next-line no-undef
const getChunkScriptFilename = __webpack_require__.u
const chunkFilenameMap = {}

// eslint-disable-next-line no-undef
__webpack_require__.u = (chunkId) => {
return chunkFilenameMap[chunkId] || getChunkScriptFilename(chunkId)
}

// Ignore the module ID transform in client.
// eslint-disable-next-line no-undef
self.__next_require__ = __webpack_require__

// eslint-disable-next-line no-undef
self.__next_chunk_load__ = (chunk) => {
if (chunk.endsWith('.css')) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = '/_next/' + chunk
document.head.appendChild(link)
return Promise.resolve()
}

const [chunkId, chunkFileName] = chunk.split(':')
chunkFilenameMap[chunkId] = `static/chunks/${chunkFileName}.js`

// @ts-ignore
// eslint-disable-next-line no-undef
return __webpack_chunk_load__(chunkId)
appDir: true,
}

hydrate()
3 changes: 3 additions & 0 deletions packages/next/client/components/app-router.client.tsx
Expand Up @@ -50,10 +50,12 @@ export default function AppRouter({
initialTree,
initialCanonicalUrl,
children,
hotReloader,
}: {
initialTree: FlightRouterState
initialCanonicalUrl: string
children: React.ReactNode
hotReloader?: React.ReactNode
}) {
const [{ tree, cache, pushRef, canonicalUrl }, dispatch] = React.useReducer<
typeof reducer
Expand Down Expand Up @@ -238,6 +240,7 @@ export default function AppRouter({
}}
>
{children}
{hotReloader}
</AppTreeContext.Provider>
</AppRouterContext.Provider>
</FullAppTreeContext.Provider>
Expand Down

0 comments on commit 2d98759

Please sign in to comment.