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

Add Suspense boundary to demo #37393

Merged
merged 1 commit into from
Jun 2, 2022
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
58 changes: 50 additions & 8 deletions packages/next/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ function pathToUrlPath(pathname: string) {
return urlPath
}

async function resolveLayoutPathsByPage({
async function resolvePathsByPage({
name,
pagePath,
resolve,
}: {
name: 'layout' | 'loading'
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
}) {
const layoutPaths = new Map<string, string | undefined>()
const paths = new Map<string, string | undefined>()
const parts = pagePath.split('/')
const isNewRootLayout =
parts[1]?.length > 2 && parts[1]?.startsWith('(') && parts[1]?.endsWith(')')
Expand All @@ -32,26 +34,30 @@ async function resolveLayoutPathsByPage({
if (!pathWithoutSlashLayout) {
continue
}
const layoutPath = `${pathWithoutSlashLayout}/layout`
const layoutPath = `${pathWithoutSlashLayout}/${name}`
let resolvedLayoutPath = await resolve(layoutPath)
let urlPath = pathToUrlPath(pathWithoutSlashLayout)

// if we are in a new root app/(root) and a custom root layout was
// not provided or a root layout app/layout is not present, we use
// a default root layout to provide the html/body tags
const isCustomRootLayout = isNewRootLayout && i === 2
const isCustomRootLayout = name === 'layout' && isNewRootLayout && i === 2

if ((isCustomRootLayout || i === 1) && !resolvedLayoutPath) {
if (
name === 'layout' &&
(isCustomRootLayout || i === 1) &&
!resolvedLayoutPath
) {
resolvedLayoutPath = await resolve('next/dist/lib/app-layout')
}
layoutPaths.set(urlPath, resolvedLayoutPath)
paths.set(urlPath, resolvedLayoutPath)

// if we're in a new root layout don't add the top-level app/layout
if (isCustomRootLayout) {
break
}
}
return layoutPaths
return paths
}

const nextAppLoader: webpack.LoaderDefinitionFunction<{
Expand All @@ -75,7 +81,39 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
}
const resolve = this.getResolve(resolveOptions)

const layoutPaths = await resolveLayoutPathsByPage({
const loadingPaths = await resolvePathsByPage({
name: 'loading',
pagePath: pagePath,
resolve: async (pathname) => {
try {
return await resolve(this.rootContext, pathname)
} catch (err: any) {
if (err.message.includes("Can't resolve")) {
return undefined
}
throw err
}
},
})

const loadingComponentsCode = []
for (const [loadingPath, resolvedLoadingPath] of loadingPaths) {
if (resolvedLoadingPath) {
this.addDependency(resolvedLoadingPath)
// use require so that we can bust the require cache
const codeLine = `'${loadingPath}': () => require('${resolvedLoadingPath}')`
loadingComponentsCode.push(codeLine)
} else {
for (const ext of extensions) {
this.addMissingDependency(
path.join(appDir, loadingPath, `layout${ext}`)
)
}
}
}

const layoutPaths = await resolvePathsByPage({
name: 'layout',
pagePath: pagePath,
resolve: async (pathname) => {
try {
Expand Down Expand Up @@ -117,6 +155,10 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
${componentsCode.join(',\n')}
};

export const loadingComponents = {
${loadingComponentsCode.join(',\n')}
};

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

Expand Down
21 changes: 18 additions & 3 deletions packages/next/server/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export type RenderOptsPartial = {

export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial

function interopDefault(mod: any) {
return mod.default || mod
}

const rscCache = new Map()

// Shadowing check does not work with TypeScript enums
Expand Down Expand Up @@ -236,7 +240,7 @@ export async function renderToHTML(
.sort()
.map((path) => {
const mod = ComponentMod.components[path]()
mod.Component = mod.default || mod
mod.Component = interopDefault(mod)
mod.path = path
return mod
})
Expand Down Expand Up @@ -307,6 +311,9 @@ export async function renderToHTML(
}

const LayoutRouter = ComponentMod.LayoutRouter
const getLoadingMod = ComponentMod.loadingComponents[layout.path]
const Loading = getLoadingMod ? interopDefault(getLoadingMod()) : null

// eslint-disable-next-line no-loop-func
const lastComponent = WrappedComponent
WrappedComponent = (props: any) => {
Expand All @@ -333,16 +340,24 @@ export async function renderToHTML(
{},
null
)

// TODO: add tests for loading.js
const chilrenWithLoading = Loading ? (
<React.Suspense fallback={<Loading />}>{children}</React.Suspense>
) : (
children
)

// Pages don't need to be wrapped in a router
return React.createElement(
layout.Component,
props,
layout.path.endsWith('/page') ? (
children
chilrenWithLoading
) : (
// TODO: only provide the part of the url that is relevant to the layout (see layout-router.client.tsx)
<LayoutRouter initialUrl={pathname} layoutPath={layout.path}>
{children}
{chilrenWithLoading}
</LayoutRouter>
)
)
Expand Down