rsnext/packages/next/build/webpack/plugins/build-stats-plugin.ts
Tobias Koppers 4f212ee91d
the way towards webpack 5 typings (#29105)
Co-authored-by: sokra <sokra@users.noreply.github.com>
2021-09-21 19:17:16 +02:00

173 lines
4.5 KiB
TypeScript

import fs from 'fs'
import path from 'path'
import { Transform, TransformCallback } from 'stream'
// @ts-ignore no types package
import bfj from 'next/dist/compiled/bfj'
import { spans } from './profiling-plugin'
import { isWebpack5 } from 'next/dist/compiled/webpack/webpack'
import type webpack from 'webpack'
import type webpack4 from 'webpack4'
import type webpack5 from 'webpack5'
const STATS_VERSION = 0
function reduceSize(stats: any) {
stats.chunks = stats.chunks.map((chunk: any) => {
const reducedChunk: any = {
id: chunk.id,
files: chunk.files,
size: chunk.size,
}
for (const module of chunk.modules) {
if (!module.id) {
continue
}
if (!reducedChunk.modules) {
reducedChunk.modules = []
}
reducedChunk.modules.push(module.id)
}
return reducedChunk
})
stats.modules = stats.modules.map((module: any) => {
const reducedModule: any = {
type: module.type,
moduleType: module.moduleType,
size: module.size,
name: module.name,
}
if (module.reasons) {
for (const reason of module.reasons) {
if (!reason.moduleName || reason.moduleId === module.id) {
continue
}
if (!reducedModule.reasons) {
reducedModule.reasons = []
}
reducedModule.reasons.push(reason.moduleId)
}
}
return [module.id, reducedModule]
})
for (const entrypointName in stats.entrypoints) {
delete stats.entrypoints[entrypointName].assets
}
return stats
}
const THRESHOLD = 16 * 1024
class BufferingStream extends Transform {
private items: Buffer[] = []
private itemsSize = 0
_transform(
chunk: Buffer | string,
encoding: BufferEncoding,
callback: TransformCallback
): void {
const buffer = Buffer.isBuffer(chunk)
? chunk
: Buffer.from(chunk as string, encoding)
const size = buffer.length
if (this.itemsSize > 0 && this.itemsSize + size > THRESHOLD) {
this.push(Buffer.concat(this.items))
this.itemsSize = 0
this.items.length = 0
}
if (size > THRESHOLD) {
this.push(buffer)
} else {
this.items.push(buffer)
this.itemsSize += size
}
callback()
}
_flush(callback: TransformCallback): void {
if (this.itemsSize > 0) {
this.push(Buffer.concat(this.items))
this.itemsSize = 0
this.items.length = 0
}
callback()
}
}
// This plugin creates a stats.json for a build when enabled
export default class BuildStatsPlugin {
private distDir: string
constructor(options: { distDir: string }) {
this.distDir = options.distDir
}
apply(compiler: webpack.Compiler) {
compiler.hooks.done.tapAsync(
'NextJsBuildStats',
async (stats, callback) => {
const compilationSpan =
spans.get(stats.compilation) || spans.get(compiler)
try {
const writeStatsSpan = compilationSpan!.traceChild('NextJsBuildStats')
await writeStatsSpan.traceAsyncFn(() => {
return new Promise((resolve, reject) => {
const baseOptions = {
all: false,
cached: true,
reasons: true,
entrypoints: true,
chunks: true,
errors: false,
warnings: false,
maxModules: Infinity,
chunkModules: true,
modules: true,
}
const statsJson = reduceSize(
isWebpack5
? (stats as webpack5.Stats).toJson({
...baseOptions,
modulesSpace: Infinity,
ids: true,
})
: (stats as webpack4.Stats).toJson({
...baseOptions,
maxModules: Infinity,
})
)
const fileStream = fs.createWriteStream(
path.join(this.distDir, 'next-stats.json'),
{ highWaterMark: THRESHOLD }
)
const jsonStream = bfj.streamify({
version: STATS_VERSION,
stats: statsJson,
})
jsonStream.pipe(new BufferingStream()).pipe(fileStream)
jsonStream.on('error', reject)
fileStream.on('error', reject)
jsonStream.on('dataError', reject)
fileStream.on('close', resolve)
})
})
callback()
} catch (err) {
callback(err)
}
}
)
}
}