Skip to content

Commit

Permalink
Refactor Flight plugins to use types. (#39136)
Browse files Browse the repository at this point in the history
Cleans up both Flight plugins and leverages type inference more.
- Rename plugin
- Update name
- Remove note on webpack5
- Add types for Flight manifest
- Use webpack5 type and tapPromise
- Remove any



## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
  • Loading branch information
timneutkens committed Jul 28, 2022
1 parent a08415b commit c567d7d
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 64 deletions.
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
}
}

0 comments on commit c567d7d

Please sign in to comment.