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 Server Action Webpack plugin #71721

Merged
merged 4 commits into from
Oct 24, 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
1 change: 1 addition & 0 deletions packages/next/src/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export function getRSCModuleInformation(
return {
type,
actions,
actionIds: parsedActionsMeta,
clientRefs,
clientEntryType,
isClientRef,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { generateActionId } from './utils'

export type NextFlightActionEntryLoaderOptions = {
actions: string
encryptionKey: string
}

function nextFlightActionEntryLoader(this: any) {
const { actions, encryptionKey }: NextFlightActionEntryLoaderOptions =
this.getOptions()
const { actions }: NextFlightActionEntryLoaderOptions = this.getOptions()

const actionList = JSON.parse(actions) as [string, string[]][]
const actionList = JSON.parse(actions) as [
string,
[id: string, name: string][],
][]
const individualActions = actionList
.map(([path, names]) => {
return names.map((name) => {
const id = generateActionId(encryptionKey, path, name)
return [id, path, name] as [string, string, string]
.map(([path, actionsFromModule]) => {
return actionsFromModule.map(([id, name]) => {
return [id, path, name]
})
})
.flat()
Expand Down
19 changes: 4 additions & 15 deletions packages/next/src/build/webpack/loaders/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type webpack from 'webpack'
import { createHash } from 'crypto'
import { RSC_MODULE_TYPES } from '../../../shared/lib/constants'

const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif', 'ico', 'svg']
Expand Down Expand Up @@ -47,25 +46,15 @@ export function isCSSMod(mod: {
export function getActionsFromBuildInfo(mod: {
resource: string
buildInfo?: any
}): undefined | string[] {
return mod.buildInfo?.rsc?.actions
}): undefined | Record<string, string> {
return mod.buildInfo?.rsc?.actionIds
}

export function generateActionId(
hashSalt: string,
filePath: string,
exportName: string
) {
return createHash('sha1')
.update(hashSalt + filePath + ':' + exportName)
.digest('hex')
}

export function encodeToBase64<T extends {}>(obj: T): string {
export function encodeToBase64<T extends object>(obj: T): string {
return Buffer.from(JSON.stringify(obj)).toString('base64')
}

export function decodeFromBase64<T extends {}>(str: string): T {
export function decodeFromBase64<T extends object>(str: string): T {
return JSON.parse(Buffer.from(str, 'base64').toString('utf8'))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from '../../../shared/lib/constants'
import {
getActionsFromBuildInfo,
generateActionId,
isClientComponentEntryModule,
isCSSMod,
regexCSS,
Expand All @@ -43,6 +42,8 @@ import { getModuleBuildInfo } from '../loaders/get-module-build-info'
import { getAssumedSourceType } from '../loaders/next-flight-loader'
import { isAppRouteRoute } from '../../../lib/is-app-route-route'

type ActionIdNamePair = [id: string, name: string]

interface Options {
dev: boolean
appDir: string
Expand Down Expand Up @@ -283,14 +284,17 @@ export class FlightClientEntryPlugin {

const addActionEntryList: Array<ReturnType<typeof this.injectActionEntry>> =
[]
const actionMapsPerEntry: Record<string, Map<string, string[]>> = {}
const createdActions = new Set<string>()
const actionMapsPerEntry: Record<
string,
Map<string, ActionIdNamePair[]>
> = {}
const createdActionIds = new Set<string>()

// For each SC server compilation entry, we need to create its corresponding
// client component entry.
forEachEntryModule(compilation, ({ name, entryModule }) => {
const internalClientComponentEntryImports: ClientComponentImports = {}
const actionEntryImports = new Map<string, string[]>()
const actionEntryImports = new Map<string, ActionIdNamePair[]>()
const clientEntriesToInject = []
const mergedCSSimports: CssImports = {}

Expand All @@ -310,8 +314,8 @@ export class FlightClientEntryPlugin {
resolvedModule: connection.resolvedModule,
})

actionImports.forEach(([dep, names]) =>
actionEntryImports.set(dep, names)
actionImports.forEach(([dep, actions]) =>
actionEntryImports.set(dep, actions)
)

const isAbsoluteRequest = path.isAbsolute(entryRequest)
Expand Down Expand Up @@ -429,7 +433,7 @@ export class FlightClientEntryPlugin {
actions: actionEntryImports,
entryName: name,
bundlePath: name,
createdActions,
createdActionIds,
})
)
}
Expand Down Expand Up @@ -458,7 +462,10 @@ export class FlightClientEntryPlugin {
await Promise.all(addActionEntryList)

const addedClientActionEntryList: Promise<any>[] = []
const actionMapsPerClientEntry: Record<string, Map<string, string[]>> = {}
const actionMapsPerClientEntry: Record<
string,
Map<string, ActionIdNamePair[]>
> = {}

// We need to create extra action entries that are created from the
// client layer.
Expand All @@ -484,21 +491,21 @@ export class FlightClientEntryPlugin {
}
}

for (const [name, actionEntryImports] of Object.entries(
for (const [entryName, actionEntryImports] of Object.entries(
actionMapsPerClientEntry
)) {
// If an action method is already created in the server layer, we don't
// need to create it again in the action layer.
// This is to avoid duplicate action instances and make sure the module
// state is shared.
let remainingClientImportedActions = false
const remainingActionEntryImports = new Map<string, string[]>()
for (const [dep, actionNames] of actionEntryImports) {
const remainingActionEntryImports = new Map<string, ActionIdNamePair[]>()
for (const [dep, actions] of actionEntryImports) {
const remainingActionNames = []
for (const actionName of actionNames) {
const id = name + '@' + dep + '@' + actionName
if (!createdActions.has(id)) {
remainingActionNames.push(actionName)
for (const action of actions) {
// `action` is a [id, name] pair.
if (!createdActionIds.has(entryName + '@' + action[0])) {
remainingActionNames.push(action)
}
}
if (remainingActionNames.length > 0) {
Expand All @@ -513,10 +520,10 @@ export class FlightClientEntryPlugin {
compiler,
compilation,
actions: remainingActionEntryImports,
entryName: name,
bundlePath: name,
entryName,
bundlePath: entryName,
fromClient: true,
createdActions,
createdActionIds,
})
)
}
Expand All @@ -533,7 +540,7 @@ export class FlightClientEntryPlugin {
dependencies: ReturnType<typeof webpack.EntryPlugin.createDependency>[]
}) {
// action file path -> action names
const collectedActions = new Map<string, string[]>()
const collectedActions = new Map<string, ActionIdNamePair[]>()

// Keep track of checked modules to avoid infinite loops with recursive imports.
const visitedModule = new Set<string>()
Expand All @@ -558,7 +565,7 @@ export class FlightClientEntryPlugin {

const actions = getActionsFromBuildInfo(mod)
if (actions) {
collectedActions.set(modResource, actions)
collectedActions.set(modResource, Object.entries(actions))
}

// Collect used exported actions transversely.
Expand Down Expand Up @@ -617,14 +624,14 @@ export class FlightClientEntryPlugin {
}): {
cssImports: CssImports
clientComponentImports: ClientComponentImports
actionImports: [string, string[]][]
actionImports: [string, ActionIdNamePair[]][]
} {
// Keep track of checked modules to avoid infinite loops with recursive imports.
const visitedOfClientComponentsTraverse = new Set()

// Info to collect.
const clientComponentImports: ClientComponentImports = {}
const actionImports: [string, string[]][] = []
const actionImports: [string, ActionIdNamePair[]][] = []
const CSSImports = new Set<string>()

const filterClientComponents = (
Expand Down Expand Up @@ -652,7 +659,7 @@ export class FlightClientEntryPlugin {

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

if (isCSSMod(mod)) {
Expand Down Expand Up @@ -839,20 +846,20 @@ export class FlightClientEntryPlugin {
entryName,
bundlePath,
fromClient,
createdActions,
createdActionIds,
}: {
compiler: webpack.Compiler
compilation: webpack.Compilation
actions: Map<string, string[]>
actions: Map<string, ActionIdNamePair[]>
entryName: string
bundlePath: string
createdActions: Set<string>
createdActionIds: Set<string>
fromClient?: boolean
}) {
const actionsArray = Array.from(actions.entries())
for (const [dep, actionNames] of actions) {
for (const actionName of actionNames) {
createdActions.add(entryName + '@' + dep + '@' + actionName)
for (const [, actionsFromModule] of actions) {
for (const [id] of actionsFromModule) {
createdActionIds.add(entryName + '@' + id)
}
}

Expand All @@ -862,17 +869,15 @@ export class FlightClientEntryPlugin {

const actionLoader = `next-flight-action-entry-loader?${stringify({
actions: JSON.stringify(actionsArray),
encryptionKey: this.encryptionKey,
__client_imported__: fromClient,
})}!`

const currentCompilerServerActions = this.isEdgeServer
? pluginState.edgeServerActions
: pluginState.serverActions

for (const [actionFilePath, actionNames] of actionsArray) {
for (const name of actionNames) {
const id = generateActionId(this.encryptionKey, actionFilePath, name)
for (const [, actionsFromModule] of actionsArray) {
for (const [id] of actionsFromModule) {
if (typeof currentCompilerServerActions[id] === 'undefined') {
currentCompilerServerActions[id] = {
workers: {},
Expand Down
Loading