2022-05-24 16:54:26 +02:00
|
|
|
import { stringify } from 'querystring'
|
2022-08-12 15:01:19 +02:00
|
|
|
import path from 'path'
|
|
|
|
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
|
2022-05-24 16:54:26 +02:00
|
|
|
import {
|
|
|
|
getInvalidator,
|
|
|
|
entries,
|
2022-08-12 15:01:19 +02:00
|
|
|
EntryTypes,
|
2022-05-24 16:54:26 +02:00
|
|
|
} from '../../../server/dev/on-demand-entry-handler'
|
2022-08-12 15:01:19 +02:00
|
|
|
import type {
|
|
|
|
CssImports,
|
|
|
|
ClientComponentImports,
|
|
|
|
NextFlightClientEntryLoaderOptions,
|
|
|
|
} from '../loaders/next-flight-client-entry-loader'
|
2022-09-30 20:32:32 +02:00
|
|
|
import { APP_DIR_ALIAS, WEBPACK_LAYERS } from '../../../lib/constants'
|
2022-08-12 15:01:19 +02:00
|
|
|
import {
|
|
|
|
COMPILER_NAMES,
|
2022-09-28 17:06:10 +02:00
|
|
|
EDGE_RUNTIME_WEBPACK,
|
2022-08-12 15:01:19 +02:00
|
|
|
FLIGHT_SERVER_CSS_MANIFEST,
|
|
|
|
} from '../../../shared/lib/constants'
|
2022-09-27 00:46:01 +02:00
|
|
|
import { FlightCSSManifest, traverseModules } from './flight-manifest-plugin'
|
|
|
|
import { ASYNC_CLIENT_MODULES } from './flight-manifest-plugin'
|
2022-10-13 13:26:12 +02:00
|
|
|
import { isClientComponentModule, regexCSS } from '../loaders/utils'
|
2022-05-24 16:54:26 +02:00
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
interface Options {
|
2022-05-24 16:54:26 +02:00
|
|
|
dev: boolean
|
|
|
|
isEdgeServer: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
const PLUGIN_NAME = 'ClientEntryPlugin'
|
|
|
|
|
|
|
|
export const injectedClientEntries = new Map()
|
2022-08-12 15:01:19 +02:00
|
|
|
|
2022-09-30 20:32:32 +02:00
|
|
|
export const serverModuleIds = new Map<string, string | number>()
|
|
|
|
export const edgeServerModuleIds = new Map<string, string | number>()
|
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
// TODO-APP: move CSS manifest generation to the flight manifest plugin.
|
|
|
|
const flightCSSManifest: FlightCSSManifest = {}
|
|
|
|
|
2022-07-29 00:35:52 +02:00
|
|
|
export class FlightClientEntryPlugin {
|
2022-08-12 15:01:19 +02:00
|
|
|
dev: boolean
|
2022-05-24 16:54:26 +02:00
|
|
|
isEdgeServer: boolean
|
|
|
|
|
|
|
|
constructor(options: Options) {
|
2022-08-12 15:01:19 +02:00
|
|
|
this.dev = options.dev
|
2022-05-24 16:54:26 +02:00
|
|
|
this.isEdgeServer = options.isEdgeServer
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:55:37 +02:00
|
|
|
apply(compiler: webpack.Compiler) {
|
2022-05-24 16:54:26 +02:00
|
|
|
compiler.hooks.compilation.tap(
|
|
|
|
PLUGIN_NAME,
|
2022-07-29 00:35:52 +02:00
|
|
|
(compilation, { normalModuleFactory }) => {
|
2022-05-24 16:54:26 +02:00
|
|
|
compilation.dependencyFactories.set(
|
|
|
|
(webpack as any).dependencies.ModuleDependency,
|
|
|
|
normalModuleFactory
|
|
|
|
)
|
|
|
|
compilation.dependencyTemplates.set(
|
|
|
|
(webpack as any).dependencies.ModuleDependency,
|
|
|
|
new (webpack as any).dependencies.NullDependency.Template()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-07-29 00:35:52 +02:00
|
|
|
compiler.hooks.finishMake.tapPromise(PLUGIN_NAME, (compilation) => {
|
2022-09-21 20:45:33 +02:00
|
|
|
return this.createClientEntries(compiler, compilation)
|
2022-07-29 00:35:52 +02:00
|
|
|
})
|
2022-09-27 00:46:01 +02:00
|
|
|
|
|
|
|
compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
|
|
|
|
traverseModules(compilation, (mod) => {
|
|
|
|
// The module must has request, and resource so it's not a new entry created with loader.
|
|
|
|
// Using the client layer module, which doesn't have `rsc` tag in buildInfo.
|
|
|
|
if (mod.request && mod.resource && !mod.buildInfo.rsc) {
|
|
|
|
if (compilation.moduleGraph.isAsync(mod)) {
|
|
|
|
ASYNC_CLIENT_MODULES.add(mod.resource)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2022-09-30 20:32:32 +02:00
|
|
|
|
|
|
|
const recordModule = (id: number | string, mod: any) => {
|
|
|
|
const modResource = mod.resourceResolveData?.path || mod.resource
|
|
|
|
|
|
|
|
if (
|
|
|
|
mod.resourceResolveData?.context?.issuerLayer ===
|
|
|
|
WEBPACK_LAYERS.server
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof id !== 'undefined' && modResource) {
|
|
|
|
// Note that this isn't that reliable as webpack is still possible to assign
|
|
|
|
// additional queries to make sure there's no conflict even using the `named`
|
|
|
|
// module ID strategy.
|
2022-10-12 15:41:19 +02:00
|
|
|
let ssrNamedModuleId = path.relative(compiler.context, modResource)
|
2022-09-30 20:32:32 +02:00
|
|
|
if (!ssrNamedModuleId.startsWith('.')) {
|
|
|
|
// TODO use getModuleId instead
|
|
|
|
ssrNamedModuleId = `./${ssrNamedModuleId.replace(/\\/g, '/')}`
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.isEdgeServer) {
|
|
|
|
edgeServerModuleIds.set(
|
|
|
|
ssrNamedModuleId.replace(/\/next\/dist\/esm\//, '/next/dist/'),
|
|
|
|
id
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
serverModuleIds.set(ssrNamedModuleId, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
compilation.chunkGroups.forEach((chunkGroup) => {
|
|
|
|
chunkGroup.chunks.forEach((chunk: webpack.Chunk) => {
|
|
|
|
const chunkModules = compilation.chunkGraph.getChunkModulesIterable(
|
|
|
|
chunk
|
|
|
|
) as Iterable<webpack.NormalModule>
|
|
|
|
|
|
|
|
for (const mod of chunkModules) {
|
|
|
|
const modId = compilation.chunkGraph.getModuleId(mod)
|
|
|
|
|
|
|
|
recordModule(modId, mod)
|
|
|
|
|
|
|
|
// If this is a concatenation, register each child to the parent ID.
|
|
|
|
// TODO: remove any
|
|
|
|
const anyModule = mod as any
|
|
|
|
if (anyModule.modules) {
|
|
|
|
anyModule.modules.forEach((concatenatedMod: any) => {
|
|
|
|
recordModule(modId, concatenatedMod)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2022-09-27 00:46:01 +02:00
|
|
|
})
|
2022-05-24 16:54:26 +02:00
|
|
|
}
|
|
|
|
|
2022-09-21 20:45:33 +02:00
|
|
|
async createClientEntries(compiler: any, compilation: any) {
|
2022-08-12 15:01:19 +02:00
|
|
|
const promises: Array<
|
|
|
|
ReturnType<typeof this.injectClientEntryAndSSRModules>
|
|
|
|
> = []
|
2022-05-24 16:54:26 +02:00
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
// Loop over all the entry modules.
|
|
|
|
function forEachEntryModule(
|
|
|
|
callback: ({
|
|
|
|
name,
|
|
|
|
entryModule,
|
|
|
|
}: {
|
|
|
|
name: string
|
|
|
|
entryModule: any
|
|
|
|
}) => void
|
|
|
|
) {
|
|
|
|
for (const [name, entry] of compilation.entries.entries()) {
|
2022-09-28 19:03:21 +02:00
|
|
|
// Skip for entries under pages/
|
|
|
|
if (name.startsWith('pages/')) continue
|
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
// Check if the page entry is a server component or not.
|
|
|
|
const entryDependency: webpack.NormalModule | undefined =
|
|
|
|
entry.dependencies?.[0]
|
|
|
|
// Ensure only next-app-loader entries are handled.
|
|
|
|
if (!entryDependency || !entryDependency.request) continue
|
|
|
|
|
|
|
|
const request = entryDependency.request
|
|
|
|
|
|
|
|
if (
|
|
|
|
!request.startsWith('next-edge-ssr-loader?') &&
|
|
|
|
!request.startsWith('next-app-loader?')
|
|
|
|
)
|
|
|
|
continue
|
2022-08-24 21:49:47 +02:00
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
let entryModule: webpack.NormalModule =
|
|
|
|
compilation.moduleGraph.getResolvedModule(entryDependency)
|
2022-05-24 16:54:26 +02:00
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
if (request.startsWith('next-edge-ssr-loader?')) {
|
|
|
|
entryModule.dependencies.forEach((dependency) => {
|
|
|
|
const modRequest: string | undefined = (dependency as any).request
|
|
|
|
if (modRequest?.includes('next-app-loader')) {
|
|
|
|
entryModule =
|
|
|
|
compilation.moduleGraph.getResolvedModule(dependency)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2022-08-12 15:01:19 +02:00
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
callback({ name, entryModule })
|
2022-08-24 21:49:47 +02:00
|
|
|
}
|
2022-09-28 17:06:10 +02:00
|
|
|
}
|
2022-08-24 21:49:47 +02:00
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
// For each SC server compilation entry, we need to create its corresponding
|
|
|
|
// client component entry.
|
|
|
|
forEachEntryModule(({ name, entryModule }) => {
|
2022-08-16 19:00:23 +02:00
|
|
|
const internalClientComponentEntryImports = new Set<
|
|
|
|
ClientComponentImports[0]
|
|
|
|
>()
|
|
|
|
|
|
|
|
for (const connection of compilation.moduleGraph.getOutgoingConnections(
|
|
|
|
entryModule
|
|
|
|
)) {
|
|
|
|
const layoutOrPageDependency = connection.dependency
|
|
|
|
const layoutOrPageRequest = connection.dependency.request
|
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
const [clientComponentImports] =
|
2022-08-16 19:00:23 +02:00
|
|
|
this.collectClientComponentsAndCSSForDependency({
|
|
|
|
layoutOrPageRequest,
|
|
|
|
compilation,
|
|
|
|
dependency: layoutOrPageDependency,
|
|
|
|
})
|
|
|
|
|
|
|
|
const isAbsoluteRequest = layoutOrPageRequest[0] === '/'
|
|
|
|
|
|
|
|
// Next.js internals are put into a separate entry.
|
|
|
|
if (!isAbsoluteRequest) {
|
|
|
|
clientComponentImports.forEach((value) =>
|
|
|
|
internalClientComponentEntryImports.add(value)
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const relativeRequest = isAbsoluteRequest
|
|
|
|
? path.relative(compilation.options.context, layoutOrPageRequest)
|
|
|
|
: layoutOrPageRequest
|
|
|
|
|
|
|
|
// Replace file suffix as `.js` will be added.
|
2022-09-18 02:00:16 +02:00
|
|
|
const bundlePath = relativeRequest.replace(/\.(js|ts)x?$/, '')
|
2022-08-12 15:01:19 +02:00
|
|
|
|
2022-08-16 19:00:23 +02:00
|
|
|
promises.push(
|
|
|
|
this.injectClientEntryAndSSRModules({
|
|
|
|
compiler,
|
|
|
|
compilation,
|
|
|
|
entryName: name,
|
|
|
|
clientComponentImports,
|
|
|
|
bundlePath,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2022-05-24 16:54:26 +02:00
|
|
|
|
2022-08-16 19:00:23 +02:00
|
|
|
// Create internal app
|
2022-08-12 15:01:19 +02:00
|
|
|
promises.push(
|
2022-08-16 19:00:23 +02:00
|
|
|
this.injectClientEntryAndSSRModules({
|
2022-08-12 15:01:19 +02:00
|
|
|
compiler,
|
|
|
|
compilation,
|
2022-08-16 19:00:23 +02:00
|
|
|
entryName: name,
|
|
|
|
clientComponentImports: [...internalClientComponentEntryImports],
|
|
|
|
bundlePath: 'app-internals',
|
|
|
|
})
|
2022-08-12 15:01:19 +02:00
|
|
|
)
|
2022-09-28 17:06:10 +02:00
|
|
|
})
|
|
|
|
|
2022-10-12 15:41:19 +02:00
|
|
|
// After optimizing all the modules, we collect the CSS that are still used
|
|
|
|
// by the certain chunk.
|
2022-09-28 17:06:10 +02:00
|
|
|
compilation.hooks.afterOptimizeModules.tap(PLUGIN_NAME, () => {
|
2022-10-12 15:41:19 +02:00
|
|
|
const cssImportsForChunk: Record<string, string[]> = {}
|
|
|
|
|
|
|
|
function collectModule(entryName: string, mod: any) {
|
|
|
|
const resource = mod.resource
|
|
|
|
if (resource) {
|
|
|
|
if (regexCSS.test(resource)) {
|
|
|
|
cssImportsForChunk[entryName].push(resource)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
compilation.chunkGroups.forEach((chunkGroup: any) => {
|
|
|
|
chunkGroup.chunks.forEach((chunk: webpack.Chunk) => {
|
|
|
|
// Here we only track page chunks.
|
|
|
|
if (!chunk.name) return
|
|
|
|
if (!chunk.name.endsWith('/page')) return
|
|
|
|
|
|
|
|
const entryName = path.join(compiler.context, chunk.name)
|
|
|
|
|
|
|
|
if (!cssImportsForChunk[entryName]) {
|
|
|
|
cssImportsForChunk[entryName] = []
|
|
|
|
}
|
|
|
|
|
|
|
|
const chunkModules = compilation.chunkGraph.getChunkModulesIterable(
|
|
|
|
chunk
|
|
|
|
) as Iterable<webpack.NormalModule>
|
|
|
|
for (const mod of chunkModules) {
|
|
|
|
collectModule(entryName, mod)
|
|
|
|
|
|
|
|
const anyModule = mod as any
|
|
|
|
if (anyModule.modules) {
|
|
|
|
anyModule.modules.forEach((concatenatedMod: any) => {
|
|
|
|
collectModule(entryName, concatenatedMod)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const entryCSSInfo: Record<string, string[]> =
|
|
|
|
flightCSSManifest.__entry_css__ || {}
|
|
|
|
entryCSSInfo[entryName] = cssImportsForChunk[entryName]
|
|
|
|
|
|
|
|
Object.assign(flightCSSManifest, {
|
|
|
|
__entry_css__: entryCSSInfo,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-09-28 17:06:10 +02:00
|
|
|
forEachEntryModule(({ entryModule }) => {
|
|
|
|
for (const connection of compilation.moduleGraph.getOutgoingConnections(
|
|
|
|
entryModule
|
|
|
|
)) {
|
|
|
|
const layoutOrPageDependency = connection.dependency
|
|
|
|
const layoutOrPageRequest = connection.dependency.request
|
|
|
|
|
|
|
|
const [, cssImports] =
|
|
|
|
this.collectClientComponentsAndCSSForDependency({
|
|
|
|
layoutOrPageRequest,
|
|
|
|
compilation,
|
|
|
|
dependency: layoutOrPageDependency,
|
|
|
|
})
|
|
|
|
|
|
|
|
Object.assign(flightCSSManifest, cssImports)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2022-08-12 15:01:19 +02:00
|
|
|
|
|
|
|
compilation.hooks.processAssets.tap(
|
|
|
|
{
|
|
|
|
name: PLUGIN_NAME,
|
|
|
|
// Have to be in the optimize stage to run after updating the CSS
|
|
|
|
// asset hash via extract mini css plugin.
|
|
|
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH,
|
|
|
|
},
|
2022-08-16 11:55:37 +02:00
|
|
|
(assets: webpack.Compilation['assets']) => {
|
2022-08-24 21:49:47 +02:00
|
|
|
const manifest = JSON.stringify(flightCSSManifest)
|
2022-08-12 15:01:19 +02:00
|
|
|
assets[FLIGHT_SERVER_CSS_MANIFEST + '.json'] = new sources.RawSource(
|
2022-08-24 21:49:47 +02:00
|
|
|
manifest
|
|
|
|
) as unknown as webpack.sources.RawSource
|
|
|
|
assets[FLIGHT_SERVER_CSS_MANIFEST + '.js'] = new sources.RawSource(
|
|
|
|
'self.__RSC_CSS_MANIFEST=' + manifest
|
2022-08-16 11:55:37 +02:00
|
|
|
) as unknown as webpack.sources.RawSource
|
2022-08-12 15:01:19 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const res = await Promise.all(promises)
|
|
|
|
|
|
|
|
// Invalidate in development to trigger recompilation
|
|
|
|
const invalidator = getInvalidator()
|
|
|
|
// Check if any of the entry injections need an invalidation
|
|
|
|
if (invalidator && res.includes(true)) {
|
|
|
|
invalidator.invalidate([COMPILER_NAMES.client])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 19:00:23 +02:00
|
|
|
collectClientComponentsAndCSSForDependency({
|
|
|
|
layoutOrPageRequest,
|
|
|
|
compilation,
|
|
|
|
dependency,
|
|
|
|
}: {
|
|
|
|
layoutOrPageRequest: string
|
|
|
|
compilation: any
|
2022-08-12 15:01:19 +02:00
|
|
|
dependency: any /* Dependency */
|
2022-08-16 19:00:23 +02:00
|
|
|
}): [ClientComponentImports, CssImports] {
|
2022-08-12 15:01:19 +02:00
|
|
|
/**
|
|
|
|
* Keep track of checked modules to avoid infinite loops with recursive imports.
|
|
|
|
*/
|
|
|
|
const visitedBySegment: { [segment: string]: Set<string> } = {}
|
|
|
|
const clientComponentImports: ClientComponentImports = []
|
|
|
|
const serverCSSImports: CssImports = {}
|
|
|
|
|
2022-08-23 01:09:56 +02:00
|
|
|
const filterClientComponents = (
|
|
|
|
dependencyToFilter: any,
|
|
|
|
inClientComponentBoundary: boolean
|
|
|
|
): void => {
|
2022-08-16 11:55:37 +02:00
|
|
|
const mod: webpack.NormalModule =
|
2022-08-12 15:01:19 +02:00
|
|
|
compilation.moduleGraph.getResolvedModule(dependencyToFilter)
|
|
|
|
if (!mod) return
|
|
|
|
|
|
|
|
// Keep client imports as simple
|
|
|
|
// native or installed js module: -> raw request, e.g. next/head
|
|
|
|
// client js or css: -> user request
|
|
|
|
const rawRequest = mod.rawRequest
|
|
|
|
|
|
|
|
// Request could be undefined or ''
|
|
|
|
if (!rawRequest) return
|
|
|
|
|
2022-10-13 13:26:12 +02:00
|
|
|
const isCSS = regexCSS.test(rawRequest)
|
2022-08-12 15:01:19 +02:00
|
|
|
const modRequest: string | undefined =
|
2022-10-13 13:26:12 +02:00
|
|
|
!isCSS &&
|
2022-08-12 15:01:19 +02:00
|
|
|
!rawRequest.startsWith('.') &&
|
|
|
|
!rawRequest.startsWith('/') &&
|
|
|
|
!rawRequest.startsWith(APP_DIR_ALIAS)
|
2022-10-13 13:26:12 +02:00
|
|
|
? rawRequest
|
|
|
|
: mod.resourceResolveData?.path + mod.resourceResolveData?.query
|
2022-08-12 15:01:19 +02:00
|
|
|
|
|
|
|
// Ensure module is not walked again if it's already been visited
|
2022-08-16 19:00:23 +02:00
|
|
|
if (!visitedBySegment[layoutOrPageRequest]) {
|
|
|
|
visitedBySegment[layoutOrPageRequest] = new Set()
|
2022-08-12 15:01:19 +02:00
|
|
|
}
|
2022-08-16 19:00:23 +02:00
|
|
|
if (
|
|
|
|
!modRequest ||
|
|
|
|
visitedBySegment[layoutOrPageRequest].has(modRequest)
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
visitedBySegment[layoutOrPageRequest].add(modRequest)
|
2022-08-12 15:01:19 +02:00
|
|
|
|
2022-09-18 02:00:16 +02:00
|
|
|
const isClientComponent = isClientComponentModule(mod)
|
2022-08-12 15:01:19 +02:00
|
|
|
|
|
|
|
if (isCSS) {
|
2022-09-28 17:06:10 +02:00
|
|
|
const sideEffectFree =
|
|
|
|
mod.factoryMeta && (mod.factoryMeta as any).sideEffectFree
|
|
|
|
|
|
|
|
if (sideEffectFree) {
|
|
|
|
const unused = !compilation.moduleGraph
|
|
|
|
.getExportsInfo(mod)
|
|
|
|
.isModuleUsed(
|
|
|
|
this.isEdgeServer ? EDGE_RUNTIME_WEBPACK : 'webpack-runtime'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (unused) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 19:00:23 +02:00
|
|
|
serverCSSImports[layoutOrPageRequest] =
|
|
|
|
serverCSSImports[layoutOrPageRequest] || []
|
|
|
|
serverCSSImports[layoutOrPageRequest].push(modRequest)
|
2022-08-12 15:01:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if request is for css file.
|
2022-08-23 01:09:56 +02:00
|
|
|
if ((!inClientComponentBoundary && isClientComponent) || isCSS) {
|
2022-08-12 15:01:19 +02:00
|
|
|
clientComponentImports.push(modRequest)
|
2022-09-30 20:32:32 +02:00
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
compilation.moduleGraph
|
|
|
|
.getOutgoingConnections(mod)
|
|
|
|
.forEach((connection: any) => {
|
2022-08-23 01:09:56 +02:00
|
|
|
filterClientComponents(
|
|
|
|
connection.dependency,
|
|
|
|
inClientComponentBoundary || isClientComponent
|
|
|
|
)
|
2022-07-11 17:23:21 +02:00
|
|
|
})
|
2022-08-12 15:01:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Traverse the module graph to find all client components.
|
2022-08-23 01:09:56 +02:00
|
|
|
filterClientComponents(dependency, false)
|
2022-08-12 15:01:19 +02:00
|
|
|
|
|
|
|
return [clientComponentImports, serverCSSImports]
|
|
|
|
}
|
|
|
|
|
2022-08-16 19:00:23 +02:00
|
|
|
async injectClientEntryAndSSRModules({
|
|
|
|
compiler,
|
|
|
|
compilation,
|
|
|
|
entryName,
|
|
|
|
clientComponentImports,
|
|
|
|
bundlePath,
|
|
|
|
}: {
|
|
|
|
compiler: any
|
|
|
|
compilation: any
|
|
|
|
entryName: string
|
2022-08-12 15:01:19 +02:00
|
|
|
clientComponentImports: ClientComponentImports
|
2022-08-16 19:00:23 +02:00
|
|
|
bundlePath: string
|
|
|
|
}): Promise<boolean> {
|
2022-08-12 15:01:19 +02:00
|
|
|
let shouldInvalidate = false
|
|
|
|
|
|
|
|
const loaderOptions: NextFlightClientEntryLoaderOptions = {
|
|
|
|
modules: clientComponentImports,
|
|
|
|
server: false,
|
|
|
|
}
|
2022-09-28 12:29:22 +02:00
|
|
|
|
|
|
|
// For the client entry, we always use the CJS build of Next.js. If the
|
|
|
|
// server is using the ESM build (when using the Edge runtime), we need to
|
|
|
|
// replace them.
|
|
|
|
const clientLoader = `next-flight-client-entry-loader?${stringify({
|
|
|
|
modules: this.isEdgeServer
|
|
|
|
? clientComponentImports.map((importPath) =>
|
|
|
|
importPath.replace('next/dist/esm/', 'next/dist/')
|
|
|
|
)
|
|
|
|
: clientComponentImports,
|
|
|
|
server: false,
|
|
|
|
})}!`
|
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
const clientSSRLoader = `next-flight-client-entry-loader?${stringify({
|
|
|
|
...loaderOptions,
|
|
|
|
server: true,
|
|
|
|
})}!`
|
|
|
|
|
|
|
|
// Add for the client compilation
|
|
|
|
// Inject the entry to the client compiler.
|
|
|
|
if (this.dev) {
|
2022-08-16 19:00:23 +02:00
|
|
|
const pageKey = COMPILER_NAMES.client + bundlePath
|
2022-08-12 15:01:19 +02:00
|
|
|
if (!entries[pageKey]) {
|
|
|
|
entries[pageKey] = {
|
|
|
|
type: EntryTypes.CHILD_ENTRY,
|
|
|
|
parentEntries: new Set([entryName]),
|
|
|
|
bundlePath,
|
|
|
|
request: clientLoader,
|
|
|
|
dispose: false,
|
|
|
|
lastActiveTime: Date.now(),
|
|
|
|
}
|
|
|
|
shouldInvalidate = true
|
|
|
|
} else {
|
|
|
|
const entryData = entries[pageKey]
|
|
|
|
// New version of the client loader
|
|
|
|
if (entryData.request !== clientLoader) {
|
|
|
|
entryData.request = clientLoader
|
|
|
|
shouldInvalidate = true
|
|
|
|
}
|
|
|
|
if (entryData.type === EntryTypes.CHILD_ENTRY) {
|
|
|
|
entryData.parentEntries.add(entryName)
|
|
|
|
}
|
2022-05-24 16:54:26 +02:00
|
|
|
}
|
2022-08-12 15:01:19 +02:00
|
|
|
} else {
|
|
|
|
injectedClientEntries.set(bundlePath, clientLoader)
|
2022-05-24 16:54:26 +02:00
|
|
|
}
|
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
// Inject the entry to the server compiler (__sc_client__).
|
|
|
|
const clientComponentEntryDep = (
|
|
|
|
webpack as any
|
|
|
|
).EntryPlugin.createDependency(clientSSRLoader, {
|
|
|
|
name: bundlePath,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Add the dependency to the server compiler.
|
|
|
|
await this.addEntry(
|
|
|
|
compilation,
|
|
|
|
// Reuse compilation context.
|
|
|
|
compiler.context,
|
|
|
|
clientComponentEntryDep,
|
|
|
|
{
|
|
|
|
// By using the same entry name
|
|
|
|
name: entryName,
|
|
|
|
// Layer should be undefined for the SSR modules
|
|
|
|
// This ensures the client components are
|
|
|
|
layer: undefined,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return shouldInvalidate
|
|
|
|
}
|
|
|
|
|
2022-08-17 11:39:37 +02:00
|
|
|
// TODO-APP: make sure dependsOn is added for layouts/pages
|
2022-08-12 15:01:19 +02:00
|
|
|
addEntry(
|
|
|
|
compilation: any,
|
|
|
|
context: string,
|
|
|
|
entry: any /* Dependency */,
|
|
|
|
options: {
|
|
|
|
name: string
|
|
|
|
layer: string | undefined
|
|
|
|
} /* EntryOptions */
|
|
|
|
): Promise<any> /* Promise<module> */ {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
compilation.entries.get(options.name).includeDependencies.push(entry)
|
|
|
|
compilation.hooks.addEntry.call(entry, options)
|
|
|
|
compilation.addModuleTree(
|
|
|
|
{
|
|
|
|
context,
|
|
|
|
dependency: entry,
|
|
|
|
contextInfo: { issuerLayer: options.layer },
|
|
|
|
},
|
|
|
|
(err: Error | undefined, module: any) => {
|
|
|
|
if (err) {
|
|
|
|
compilation.hooks.failedEntry.call(entry, options, err)
|
|
|
|
return reject(err)
|
|
|
|
}
|
2022-09-27 00:46:01 +02:00
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
compilation.hooks.succeedEntry.call(entry, options, module)
|
|
|
|
return resolve(module)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
2022-05-24 16:54:26 +02:00
|
|
|
}
|
|
|
|
}
|