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: create shared utils for mod resource #69145

Merged
merged 1 commit into from
Aug 22, 2024
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
20 changes: 17 additions & 3 deletions packages/next/src/build/webpack/loaders/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@ import { RSC_MODULE_TYPES } from '../../../shared/lib/constants'
const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif', 'ico', 'svg']
const imageRegex = new RegExp(`\\.(${imageExtensions.join('|')})$`)

// Determine if the whole module is server action, 'use server' in the top level of module
export function isActionServerLayerEntryModule(mod: {
resource: string
buildInfo?: any
}) {
const rscInfo = mod.buildInfo.rsc
return !!(rscInfo?.actions && rscInfo?.type === RSC_MODULE_TYPES.server)
}

// Determine if the whole module is client action, 'use server' in nested closure in the client module
function isActionClientLayerModule(mod: { resource: string; buildInfo?: any }) {
const rscInfo = mod.buildInfo.rsc
return !!(rscInfo?.actions && rscInfo?.type === RSC_MODULE_TYPES.client)
}

export function isClientComponentEntryModule(mod: {
resource: string
buildInfo?: any
}) {
const rscInfo = mod.buildInfo.rsc
const hasClientDirective = rscInfo?.isClientRef
const isActionLayerEntry =
rscInfo?.actions && rscInfo?.type === RSC_MODULE_TYPES.client
const isActionLayerEntry = isActionClientLayerModule(mod)
return (
hasClientDirective || isActionLayerEntry || imageRegex.test(mod.resource)
)
Expand All @@ -39,7 +53,7 @@ export function isCSSMod(mod: {
)
}

export function getActions(mod: {
export function getActionsFromBuildInfo(mod: {
resource: string
buildInfo?: any
}): undefined | string[] {
Expand Down
106 changes: 48 additions & 58 deletions packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
UNDERSCORE_NOT_FOUND_ROUTE_ENTRY,
} from '../../../shared/lib/constants'
import {
getActions,
getActionsFromBuildInfo,
generateActionId,
isClientComponentEntryModule,
isCSSMod,
Expand Down Expand Up @@ -356,10 +356,10 @@ export class FlightClientEntryPlugin {
...clientEntryToInject.clientComponentImports,
...(
dedupedCSSImports[clientEntryToInject.absolutePagePath] || []
).reduce((res, curr) => {
).reduce<ClientComponentImports>((res, curr) => {
res[curr] = new Set()
return res
}, {} as ClientComponentImports),
}, {}),
},
})

Expand Down Expand Up @@ -529,27 +529,14 @@ export class FlightClientEntryPlugin {
const collectActionsInDep = (mod: webpack.NormalModule): void => {
if (!mod) return

const modPath: string = mod.resourceResolveData?.path || ''
// We have to always use the resolved request here to make sure the
// server and client are using the same module path (required by RSC), as
// the server compiler and client compiler have different resolve configs.
let modRequest: string =
modPath + (mod.resourceResolveData?.query || '')

// For the barrel optimization, we need to use the match resource instead
// because there will be 2 modules for the same file (same resource path)
// but they're different modules and can't be deduped via `visitedModule`.
// The first module is a virtual re-export module created by the loader.
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
modRequest = mod.matchResource + ':' + modRequest
}
const modResource = getModuleResource(mod)

if (!modRequest || visitedModule.has(modRequest)) return
visitedModule.add(modRequest)
if (!modResource || visitedModule.has(modResource)) return
visitedModule.add(modResource)

const actions = getActions(mod)
const actions = getActionsFromBuildInfo(mod)
if (actions) {
collectedActions.set(modRequest, actions)
collectedActions.set(modResource, actions)
}

getModuleReferencesInOrder(mod, compilation.moduleGraph).forEach(
Expand Down Expand Up @@ -578,8 +565,8 @@ export class FlightClientEntryPlugin {
ssrEntryModule,
compilation.moduleGraph
)) {
const dependency = connection.dependency!
const request = (dependency as unknown as webpack.NormalModule).request
const depModule = connection.dependency
const request = (depModule as unknown as webpack.NormalModule).request

// It is possible that the same entry is added multiple times in the
// connection graph. We can just skip these to speed up the process.
Expand Down Expand Up @@ -624,45 +611,26 @@ export class FlightClientEntryPlugin {
if (!mod) return

const isCSS = isCSSMod(mod)
const modResource = getModuleResource(mod)

const modPath: string = mod.resourceResolveData?.path || ''
const modQuery = mod.resourceResolveData?.query || ''
// We have to always use the resolved request here to make sure the
// server and client are using the same module path (required by RSC), as
// the server compiler and client compiler have different resolve configs.
let modRequest: string = modPath + modQuery

// Context modules don't have a resource path, we use the identifier instead.
if (mod.constructor.name === 'ContextModule') {
modRequest = (mod as any)._identifier
}

// For the barrel optimization, we need to use the match resource instead
// because there will be 2 modules for the same file (same resource path)
// but they're different modules and can't be deduped via `visitedModule`.
// The first module is a virtual re-export module created by the loader.
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
modRequest = mod.matchResource + ':' + modRequest
}

if (!modRequest) return
if (visited.has(modRequest)) {
if (clientComponentImports[modRequest]) {
if (!modResource) return
if (visited.has(modResource)) {
if (clientComponentImports[modResource]) {
addClientImport(
mod,
modRequest,
modResource,
clientComponentImports,
importedIdentifiers,
false
)
}
return
}
visited.add(modRequest)
visited.add(modResource)

const actions = getActions(mod)
const actions = getActionsFromBuildInfo(mod)
if (actions) {
actionImports.push([modRequest, actions])
actionImports.push([modResource, actions])
}

const webpackRuntime = this.isEdgeServer
Expand All @@ -681,14 +649,14 @@ export class FlightClientEntryPlugin {
if (unused) return
}

CSSImports.add(modRequest)
CSSImports.add(modResource)
} else if (isClientComponentEntryModule(mod)) {
if (!clientComponentImports[modRequest]) {
clientComponentImports[modRequest] = new Set()
if (!clientComponentImports[modResource]) {
clientComponentImports[modResource] = new Set()
}
addClientImport(
mod,
modRequest,
modResource,
clientComponentImports,
importedIdentifiers,
true
Expand All @@ -700,7 +668,6 @@ export class FlightClientEntryPlugin {
getModuleReferencesInOrder(mod, compilation.moduleGraph).forEach(
(connection: any) => {
let dependencyIds: string[] = []
const depModule = connection.resolvedModule

// `ids` are the identifiers that are imported from the dependency,
// if it's present, it's an array of strings.
Expand All @@ -710,7 +677,7 @@ export class FlightClientEntryPlugin {
dependencyIds = ['*']
}

filterClientComponents(depModule, dependencyIds)
filterClientComponents(connection.resolvedModule, dependencyIds)
}
)
}
Expand Down Expand Up @@ -1029,7 +996,7 @@ function addClientImport(
modRequest: string,
clientComponentImports: ClientComponentImports,
importedIdentifiers: string[],
isFirstImport: boolean
isFirstVisitModule: boolean
) {
const clientEntryType = getModuleBuildInfo(mod).rsc?.clientEntryType
const isCjsModule = clientEntryType === 'cjs'
Expand All @@ -1044,7 +1011,7 @@ function addClientImport(
// If there's collected import path with named import identifiers,
// or there's nothing in collected imports are empty.
// we should include the whole module.
if (!isFirstImport && [...clientImportsSet][0] !== '*') {
if (!isFirstVisitModule && [...clientImportsSet][0] !== '*') {
clientComponentImports[modRequest] = new Set(['*'])
}
} else {
Expand All @@ -1069,3 +1036,26 @@ function addClientImport(
}
}
}

function getModuleResource(mod: webpack.NormalModule): string {
const modPath: string = mod.resourceResolveData?.path || ''
const modQuery = mod.resourceResolveData?.query || ''
// We have to always use the resolved request here to make sure the
// server and client are using the same module path (required by RSC), as
// the server compiler and client compiler have different resolve configs.
let modResource: string = modPath + modQuery

// Context modules don't have a resource path, we use the identifier instead.
if (mod.constructor.name === 'ContextModule') {
modResource = mod.identifier()
}

// For the barrel optimization, we need to use the match resource instead
// because there will be 2 modules for the same file (same resource path)
// but they're different modules and can't be deduped via `visitedModule`.
// The first module is a virtual re-export module created by the loader.
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
modResource = mod.matchResource + ':' + modResource
}
return modResource
}
Loading