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

Simplify the logic for static flight response generation #36984

Merged
merged 5 commits into from
May 18, 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
259 changes: 129 additions & 130 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
import {
MIDDLEWARE_FLIGHT_MANIFEST,
EDGE_RUNTIME_WEBPACK,
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
} from '../../../shared/lib/constants'
import { clientComponentRegex } from '../loaders/utils'
import { normalizePagePath } from '../../../shared/lib/page-path/normalize-page-path'
Expand Down Expand Up @@ -54,8 +55,6 @@ export class FlightManifestPlugin {
}

apply(compiler: any) {
const context = (this as any).context

compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation: any, { normalModuleFactory }: any) => {
Expand All @@ -74,151 +73,151 @@ export class FlightManifestPlugin {
compiler.hooks.finishMake.tapAsync(
PLUGIN_NAME,
async (compilation: any, callback: any) => {
const promises: any = []
this.createClientEndpoints(compilation, callback)
}
)

// For each SC server compilation entry, we need to create its corresponding
// client component entry.
for (const [name, entry] of compilation.entries.entries()) {
if (name === 'pages/_app.server') continue
compiler.hooks.make.tap(PLUGIN_NAME, (compilation: any) => {
compilation.hooks.processAssets.tap(
{
name: PLUGIN_NAME,
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
(assets: any) => this.createAsset(assets, compilation)
)
})
}

// Check if the page entry is a server component or not.
const entryDependency = entry.dependencies?.[0]
const request = entryDependency?.request
async createClientEndpoints(compilation: any, callback: () => void) {
const context = (this as any).context
const promises: any = []

if (request && entry.options?.layer === 'sc_server') {
const visited = new Set()
const clientComponentImports: string[] = []
// For each SC server compilation entry, we need to create its corresponding
// client component entry.
for (const [name, entry] of compilation.entries.entries()) {
if (name === 'pages/_app.server') continue

function filterClientComponents(dependency: any) {
const module =
compilation.moduleGraph.getResolvedModule(dependency)
if (!module) return
// Check if the page entry is a server component or not.
const entryDependency = entry.dependencies?.[0]
const request = entryDependency?.request

if (visited.has(module.userRequest)) return
visited.add(module.userRequest)
if (request && entry.options?.layer === 'sc_server') {
const visited = new Set()
const clientComponentImports: string[] = []

if (clientComponentRegex.test(module.userRequest)) {
clientComponentImports.push(module.userRequest)
}
function filterClientComponents(dependency: any) {
const module = compilation.moduleGraph.getResolvedModule(dependency)
if (!module) return

compilation.moduleGraph
.getOutgoingConnections(module)
.forEach((connection: any) => {
filterClientComponents(connection.dependency)
})
}
if (visited.has(module.userRequest)) return
visited.add(module.userRequest)

// Traverse the module graph to find all client components.
filterClientComponents(entryDependency)
if (clientComponentRegex.test(module.userRequest)) {
clientComponentImports.push(module.userRequest)
}

const entryModule =
compilation.moduleGraph.getResolvedModule(entryDependency)
const routeInfo = entryModule.buildInfo.route || {
page: denormalizePagePath(name.replace(/^pages/, '')),
absolutePagePath: entryModule.resource,
}
compilation.moduleGraph
.getOutgoingConnections(module)
.forEach((connection: any) => {
filterClientComponents(connection.dependency)
})
}

// Parse gSSP and gSP exports from the page source.
const pageStaticInfo = this.isEdgeServer
? {}
: await getPageStaticInfo(
routeInfo.absolutePagePath,
{},
this.dev
)

const clientLoader = `next-flight-client-entry-loader?${stringify({
modules: clientComponentImports,
runtime: this.isEdgeServer ? 'edge' : 'nodejs',
ssr: pageStaticInfo.ssr,
// Adding name here to make the entry key unique.
name,
})}!`

const bundlePath = 'pages' + normalizePagePath(routeInfo.page)

// Inject the entry to the client compiler.
if (this.dev) {
const pageKey = 'client' + routeInfo.page
if (!entries[pageKey]) {
entries[pageKey] = {
bundlePath,
absolutePagePath: routeInfo.absolutePagePath,
clientLoader,
dispose: false,
lastActiveTime: Date.now(),
} as any
const invalidator = getInvalidator()
if (invalidator) {
invalidator.invalidate()
}
}
} else {
injectedClientEntries.set(
bundlePath,
`next-client-pages-loader?${stringify({
isServerComponent: true,
page: denormalizePagePath(bundlePath.replace(/^pages/, '')),
absolutePagePath: clientLoader,
})}!` + clientLoader
)
}
// Traverse the module graph to find all client components.
filterClientComponents(entryDependency)

const entryModule =
compilation.moduleGraph.getResolvedModule(entryDependency)
const routeInfo = entryModule.buildInfo.route || {
page: denormalizePagePath(name.replace(/^pages/, '')),
absolutePagePath: entryModule.resource,
}

// Inject the entry to the server compiler.
const clientComponentEntryDep = (
webpack as any
).EntryPlugin.createDependency(
// Parse gSSP and gSP exports from the page source.
const pageStaticInfo = this.isEdgeServer
? {}
: await getPageStaticInfo(routeInfo.absolutePagePath, {}, this.dev)

const clientLoader = `next-flight-client-entry-loader?${stringify({
modules: clientComponentImports,
runtime: this.isEdgeServer ? 'edge' : 'nodejs',
ssr: pageStaticInfo.ssr,
// Adding name here to make the entry key unique.
name,
})}!`

const bundlePath = 'pages' + normalizePagePath(routeInfo.page)

// Inject the entry to the client compiler.
if (this.dev) {
const pageKey = 'client' + routeInfo.page
if (!entries[pageKey]) {
entries[pageKey] = {
bundlePath,
absolutePagePath: routeInfo.absolutePagePath,
clientLoader,
name + '.__sc_client__'
)
promises.push(
new Promise<void>((res, rej) => {
compilation.addEntry(
context,
clientComponentEntryDep,
this.isEdgeServer
? {
name: name + '.__sc_client__',
library: {
name: ['self._CLIENT_ENTRY'],
type: 'assign',
},
runtime: EDGE_RUNTIME_WEBPACK,
asyncChunks: false,
}
: {
name: name + '.__sc_client__',
runtime: 'webpack-runtime',
},
(err: any) => {
if (err) {
rej(err)
} else {
res()
}
}
)
})
)
dispose: false,
lastActiveTime: Date.now(),
} as any
const invalidator = getInvalidator()
if (invalidator) {
invalidator.invalidate()
}
}
} else {
injectedClientEntries.set(
bundlePath,
`next-client-pages-loader?${stringify({
isServerComponent: true,
page: denormalizePagePath(bundlePath.replace(/^pages/, '')),
absolutePagePath: clientLoader,
})}!` + clientLoader
)
}

Promise.all(promises)
.then(() => callback())
.catch(callback)
// Inject the entry to the server compiler.
const clientComponentEntryDep = (
webpack as any
).EntryPlugin.createDependency(
clientLoader,
name + NEXT_CLIENT_SSR_ENTRY_SUFFIX
)
promises.push(
new Promise<void>((res, rej) => {
compilation.addEntry(
context,
clientComponentEntryDep,
this.isEdgeServer
? {
name: name + NEXT_CLIENT_SSR_ENTRY_SUFFIX,
library: {
name: ['self._CLIENT_ENTRY'],
type: 'assign',
},
runtime: EDGE_RUNTIME_WEBPACK,
asyncChunks: false,
}
: {
name: name + NEXT_CLIENT_SSR_ENTRY_SUFFIX,
runtime: 'webpack-runtime',
},
(err: any) => {
if (err) {
rej(err)
} else {
res()
}
}
)
})
)
}
)
}

compiler.hooks.make.tap(PLUGIN_NAME, (compilation: any) => {
compilation.hooks.processAssets.tap(
{
name: PLUGIN_NAME,
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
(assets: any) => this.createAsset(assets, compilation)
)
})
Promise.all(promises)
.then(() => callback())
.catch(callback)
}

createAsset(assets: any, compilation: any) {
Expand Down
7 changes: 6 additions & 1 deletion packages/next/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
MIDDLEWARE_FLIGHT_MANIFEST,
MIDDLEWARE_MANIFEST,
MIDDLEWARE_REACT_LOADABLE_MANIFEST,
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
} from '../../../shared/lib/constants'

export interface MiddlewareManifest {
Expand Down Expand Up @@ -395,7 +396,11 @@ function getEntryFiles(entryFiles: string[], meta: EntryMetadata) {
(file) =>
file.startsWith('pages/') && !file.endsWith('.hot-update.js')
)
.map((file) => 'server/' + file.replace('.js', '.__sc_client__.js'))
.map(
(file) =>
'server/' +
file.replace('.js', NEXT_CLIENT_SSR_ENTRY_SUFFIX + '.js')
)
)
}

Expand Down
15 changes: 8 additions & 7 deletions packages/next/server/load-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BUILD_MANIFEST,
REACT_LOADABLE_MANIFEST,
MIDDLEWARE_FLIGHT_MANIFEST,
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
} from '../shared/lib/constants'
import { join } from 'path'
import { requirePage, getPagePath } from './require'
Expand Down Expand Up @@ -69,7 +70,7 @@ export async function loadComponents(
distDir: string,
pathname: string,
serverless: boolean,
serverComponents?: boolean,
hasServerComponents?: boolean,
rootEnabled?: boolean
): Promise<LoadComponentsReturnType> {
if (serverless) {
Expand Down Expand Up @@ -115,7 +116,7 @@ export async function loadComponents(
Promise.resolve().then(() =>
requirePage(pathname, distDir, serverless, rootEnabled)
),
serverComponents
hasServerComponents
? Promise.resolve().then(() =>
requirePage('/_app.server', distDir, serverless, rootEnabled)
)
Expand All @@ -126,23 +127,23 @@ export async function loadComponents(
await Promise.all([
require(join(distDir, BUILD_MANIFEST)),
require(join(distDir, REACT_LOADABLE_MANIFEST)),
serverComponents
hasServerComponents
? require(join(distDir, 'server', MIDDLEWARE_FLIGHT_MANIFEST + '.json'))
: null,
])

if (serverComponents) {
if (hasServerComponents) {
try {
// Make sure to also load the client entry in cache.
await requirePage(
normalizePagePath(pathname) + '.__sc_client__',
normalizePagePath(pathname) + NEXT_CLIENT_SSR_ENTRY_SUFFIX,
distDir,
serverless,
rootEnabled
)
} catch (_) {
// This page might not be a server component page, so there is no __sc_client__
// bundle to load.
// This page might not be a server component page, so there is no
// client entry to load.
}
}

Expand Down
Loading