rsnext/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts
2021-08-17 09:18:08 +02:00

130 lines
4.3 KiB
TypeScript

import { webpack } from 'next/dist/compiled/webpack/webpack'
import { isWebpack5 } from 'next/dist/compiled/webpack/webpack'
import { STRING_LITERAL_DROP_BUNDLE } from '../../../shared/lib/constants'
export const ampFirstEntryNamesMap: WeakMap<
webpack.compilation.Compilation,
string[]
> = new WeakMap()
const PLUGIN_NAME = 'DropAmpFirstPagesPlugin'
// Prevents outputting client pages when they are not needed
export class DropClientPage implements webpack.Plugin {
ampPages = new Set()
apply(compiler: webpack.Compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation: any, { normalModuleFactory }: any) => {
// Recursively look up the issuer till it ends up at the root
function findEntryModule(mod: any): webpack.compilation.Module | null {
const queue = new Set([mod])
for (const module of queue) {
if (isWebpack5) {
// @ts-ignore TODO: webpack 5 types
const incomingConnections =
compilation.moduleGraph.getIncomingConnections(module)
for (const incomingConnection of incomingConnections) {
if (!incomingConnection.originModule) return module
queue.add(incomingConnection.originModule)
}
continue
}
for (const reason of module.reasons) {
if (!reason.module) return module
queue.add(reason.module)
}
}
return null
}
function handler(parser: any) {
function markAsAmpFirst() {
const entryModule = findEntryModule(parser.state.module)
if (!entryModule) {
return
}
// @ts-ignore buildInfo exists on Module
entryModule.buildInfo.NEXT_ampFirst = true
}
if (isWebpack5) {
parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator: any) => {
if (declarator?.id?.name === STRING_LITERAL_DROP_BUNDLE) {
markAsAmpFirst()
}
})
return
}
parser.hooks.varDeclaration
.for(STRING_LITERAL_DROP_BUNDLE)
.tap(PLUGIN_NAME, markAsAmpFirst)
}
normalModuleFactory.hooks.parser
.for('javascript/auto')
.tap(PLUGIN_NAME, handler)
normalModuleFactory.hooks.parser
.for('javascript/esm')
.tap(PLUGIN_NAME, handler)
normalModuleFactory.hooks.parser
.for('javascript/dynamic')
.tap(PLUGIN_NAME, handler)
if (!ampFirstEntryNamesMap.has(compilation)) {
ampFirstEntryNamesMap.set(compilation, [])
}
const ampFirstEntryNamesItem = ampFirstEntryNamesMap.get(
compilation
) as string[]
compilation.hooks.seal.tap(PLUGIN_NAME, () => {
if (isWebpack5) {
for (const [name, entryData] of compilation.entries) {
for (const dependency of entryData.dependencies) {
// @ts-ignore TODO: webpack 5 types
const module = compilation.moduleGraph.getModule(dependency)
if (module?.buildInfo?.NEXT_ampFirst) {
ampFirstEntryNamesItem.push(name)
// @ts-ignore @types/webpack has outdated types for webpack 5
compilation.entries.delete(name)
}
}
}
return
}
// Remove preparedEntrypoint that has bundle drop marker
// This will ensure webpack does not create chunks/bundles for this particular entrypoint
for (
let i = compilation._preparedEntrypoints.length - 1;
i >= 0;
i--
) {
const entrypoint = compilation._preparedEntrypoints[i]
if (entrypoint?.module?.buildInfo?.NEXT_ampFirst) {
ampFirstEntryNamesItem.push(entrypoint.name)
compilation._preparedEntrypoints.splice(i, 1)
}
}
for (let i = compilation.entries.length - 1; i >= 0; i--) {
const entryModule = compilation.entries[i]
if (entryModule?.buildInfo?.NEXT_ampFirst) {
compilation.entries.splice(i, 1)
}
}
})
}
)
}
}