Skip to content

Commit

Permalink
refactor: create shared utils for mod resource (#69145)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored and lubieowoce committed Sep 3, 2024
1 parent a4ff132 commit 29d715d
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 61 deletions.
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 @@ -371,10 +371,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 @@ -544,27 +544,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 @@ -593,8 +580,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 @@ -639,45 +626,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 @@ -696,14 +664,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 @@ -715,7 +683,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 @@ -725,7 +692,7 @@ export class FlightClientEntryPlugin {
dependencyIds = ['*']
}

filterClientComponents(depModule, dependencyIds)
filterClientComponents(connection.resolvedModule, dependencyIds)
}
)
}
Expand Down Expand Up @@ -1037,7 +1004,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 @@ -1052,7 +1019,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 @@ -1077,3 +1044,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
}

0 comments on commit 29d715d

Please sign in to comment.