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 Flight plugins to use types. #39136

Merged
merged 11 commits into from Jul 28, 2022
2 changes: 1 addition & 1 deletion packages/next/build/index.ts
Expand Up @@ -111,7 +111,7 @@ import {
teardownTraceSubscriber,
teardownCrashReporter,
} from './swc'
import { injectedClientEntries } from './webpack/plugins/client-entry-plugin'
import { injectedClientEntries } from './webpack/plugins/flight-client-entry-plugin'
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'
import { flatReaddir } from '../lib/flat-readdir'
import { RemotePattern } from '../shared/lib/image-config'
Expand Down
4 changes: 2 additions & 2 deletions packages/next/build/webpack-config.ts
Expand Up @@ -48,7 +48,7 @@ import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin
import { regexLikeCss } from './webpack/config/blocks/css'
import { CopyFilePlugin } from './webpack/plugins/copy-file-plugin'
import { FlightManifestPlugin } from './webpack/plugins/flight-manifest-plugin'
import { ClientEntryPlugin } from './webpack/plugins/client-entry-plugin'
import { FlightClientEntryPlugin } from './webpack/plugins/flight-client-entry-plugin'
import type {
Feature,
SWC_TARGET_TRIPLE,
Expand Down Expand Up @@ -1723,7 +1723,7 @@ export default async function getBaseWebpackConfig(
appDir: !!config.experimental.appDir,
pageExtensions: rawPageExtensions,
})
: new ClientEntryPlugin({
: new FlightClientEntryPlugin({
dev,
isEdgeServer,
})),
Expand Down
@@ -1,5 +1,6 @@
import { stringify } from 'querystring'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
import {
EDGE_RUNTIME_WEBPACK,
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
Expand All @@ -24,7 +25,7 @@ const PLUGIN_NAME = 'ClientEntryPlugin'
export const injectedClientEntries = new Map()
const regexCSS = /\.css$/

export class ClientEntryPlugin {
export class FlightClientEntryPlugin {
dev: boolean = false
isEdgeServer: boolean

Expand All @@ -35,10 +36,10 @@ export class ClientEntryPlugin {
this.isEdgeServer = options.isEdgeServer
}

apply(compiler: any) {
apply(compiler: webpack5.Compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation: any, { normalModuleFactory }: any) => {
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
(webpack as any).dependencies.ModuleDependency,
normalModuleFactory
Expand All @@ -50,18 +51,14 @@ export class ClientEntryPlugin {
}
)

// Only for webpack 5
compiler.hooks.finishMake.tapAsync(
PLUGIN_NAME,
async (compilation: any, callback: any) => {
this.createClientEndpoints(compilation, callback)
}
)
compiler.hooks.finishMake.tapPromise(PLUGIN_NAME, (compilation) => {
return this.createClientEndpoints(compilation)
})
}

async createClientEndpoints(compilation: any, callback: () => void) {
async createClientEndpoints(compilation: any) {
const context = (this as any).context
const promises: any = []
const promises: Array<Promise<void>> = []

// For each SC server compilation entry, we need to create its corresponding
// client component entry.
Expand Down Expand Up @@ -209,8 +206,6 @@ export class ClientEntryPlugin {
}
}

Promise.all(promises)
.then(() => callback())
.catch(callback)
await Promise.all(promises)
}
}
136 changes: 90 additions & 46 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Expand Up @@ -18,31 +18,58 @@ import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
// you might want them.
// const clientFileName = require.resolve('../');

type Options = {
interface Options {
dev: boolean
appDir: boolean
pageExtensions: string[]
}

type ModuleId = string | number

type ManifestChunks = Array<`${string}:${string}` | string>

interface ManifestNode {
[moduleExport: string]: {
/**
* Webpack module id
*/
id: ModuleId
/**
* Export name
*/
name: string
/**
* Chunks for the module. JS and CSS.
*/
chunks: ManifestChunks
}
}

type FlightManifest = {
__ssr_module_mapping__: {
[moduleId: string]: ManifestNode
}
} & {
[modulePath: string]: ManifestNode
}

const PLUGIN_NAME = 'FlightManifestPlugin'

export class FlightManifestPlugin {
dev: boolean = false
pageExtensions: string[]
appDir: boolean = false
dev: Options['dev'] = false
pageExtensions: Options['pageExtensions']
appDir: Options['appDir'] = false

constructor(options: Options) {
if (typeof options.dev === 'boolean') {
this.dev = options.dev
}
this.dev = options.dev
this.appDir = options.appDir
this.pageExtensions = options.pageExtensions
}

apply(compiler: any) {
apply(compiler: webpack5.Compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation: any, { normalModuleFactory }: any) => {
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
(webpack as any).dependencies.ModuleDependency,
normalModuleFactory
Expand All @@ -54,7 +81,7 @@ export class FlightManifestPlugin {
}
)

compiler.hooks.make.tap(PLUGIN_NAME, (compilation: any) => {
compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.processAssets.tap(
{
name: PLUGIN_NAME,
Expand All @@ -63,21 +90,27 @@ export class FlightManifestPlugin {
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH,
},
(assets: any) => this.createAsset(assets, compilation, compiler.context)
(assets) => this.createAsset(assets, compilation, compiler.context)
)
})
}

createAsset(assets: any, compilation: webpack5.Compilation, context: string) {
const manifest: any = {}
createAsset(
assets: webpack5.Compilation['assets'],
compilation: webpack5.Compilation,
context: string
) {
const manifest: FlightManifest = {
__ssr_module_mapping__: {},
}
const appDir = this.appDir
const dev = this.dev

compilation.chunkGroups.forEach((chunkGroup: any) => {
compilation.chunkGroups.forEach((chunkGroup) => {
function recordModule(
chunk: webpack5.Chunk,
id: string | number,
mod: any
id: ModuleId,
mod: webpack5.NormalModule
) {
// if appDir is enabled we shouldn't process chunks from
// the pages dir
Expand All @@ -89,22 +122,23 @@ export class FlightManifestPlugin {
mod.type === 'css/mini-extract' ||
(mod.loaders &&
(dev
? mod.loaders.some((item: any) =>
? mod.loaders.some((item) =>
item.loader.includes('next-style-loader/index.js')
)
: mod.loaders.some((item: any) =>
: mod.loaders.some((item) =>
item.loader.includes('mini-css-extract-plugin/loader.js')
)))

const resource =
mod.type === 'css/mini-extract'
? mod._identifier.slice(mod._identifier.lastIndexOf('!') + 1)
? // @ts-expect-error TODO: use `identifier()` instead.
mod._identifier.slice(mod._identifier.lastIndexOf('!') + 1)
: mod.resource

if (!resource) return

const moduleExports: any = manifest[resource] || {}
const moduleIdMapping: any = manifest.__ssr_module_mapping__ || {}
const moduleExports = manifest[resource] || {}
const moduleIdMapping = manifest.__ssr_module_mapping__
moduleIdMapping[id] = moduleIdMapping[id] || {}

// Note that this isn't that reliable as webpack is still possible to assign
Expand Down Expand Up @@ -146,27 +180,26 @@ export class FlightManifestPlugin {

const exportsInfo = compilation.moduleGraph.getExportsInfo(mod)
const cjsExports = [
...new Set(
[].concat(
mod.dependencies.map((dep: any) => {
// Match CommonJsSelfReferenceDependency
if (dep.type === 'cjs self exports reference') {
// `module.exports = ...`
if (dep.base === 'module.exports') {
return 'default'
}

// `exports.foo = ...`, `exports.default = ...`
if (dep.base === 'exports') {
return dep.names.filter(
(name: any) => name !== '__esModule'
)
}
...new Set([
...mod.dependencies.map((dep) => {
// Match CommonJsSelfReferenceDependency
if (dep.type === 'cjs self exports reference') {
// `module.exports = ...`
// @ts-expect-error: TODO: Fix Dependency type
if (dep.base === 'module.exports') {
return 'default'
}
return null
})
)
),

// `exports.foo = ...`, `exports.default = ...`
// @ts-expect-error: TODO: Fix Dependency type
if (dep.base === 'exports') {
// @ts-expect-error: TODO: Fix Dependency type
return dep.names.filter((name: any) => name !== '__esModule')
}
}
return null
}),
]),
]

const moduleExportedKeys = ['', '*']
Expand Down Expand Up @@ -210,7 +243,7 @@ export class FlightManifestPlugin {
collectClientImportedCss(mod)

moduleExportedKeys.forEach((name) => {
let requiredChunks = []
let requiredChunks: ManifestChunks = []
if (!moduleExports[name]) {
const isRelatedChunk = (c: webpack5.Chunk) =>
// If current chunk is a page, it should require the related page chunk;
Expand Down Expand Up @@ -249,14 +282,17 @@ export class FlightManifestPlugin {
}

chunkGroup.chunks.forEach((chunk: webpack5.Chunk) => {
const chunkModules =
compilation.chunkGraph.getChunkModulesIterable(chunk)
const chunkModules = compilation.chunkGraph.getChunkModulesIterable(
chunk
// TODO: Update type so that it doesn't have to be cast.
) as Iterable<webpack5.NormalModule>
for (const mod of chunkModules) {
const modId = compilation.chunkGraph.getModuleId(mod)

recordModule(chunk, 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) => {
Expand All @@ -270,7 +306,15 @@ export class FlightManifestPlugin {
const file = 'server/' + FLIGHT_MANIFEST
const json = JSON.stringify(manifest)

assets[file + '.js'] = new sources.RawSource('self.__RSC_MANIFEST=' + json)
assets[file + '.json'] = new sources.RawSource(json)
assets[file + '.js'] = new sources.RawSource(
'self.__RSC_MANIFEST=' + json
// Work around webpack 4 type of RawSource being used
// TODO: use webpack 5 type by default
) as unknown as webpack5.sources.RawSource
assets[file + '.json'] = new sources.RawSource(
json
// Work around webpack 4 type of RawSource being used
// TODO: use webpack 5 type by default
) as unknown as webpack5.sources.RawSource
}
}