UNPKG

rollup-plugin-webpack-stats

Version:

Rollup/Vite/Rolldown plugin to generate a stats JSON file with a bundle-stats webpack-compatible structure

1 lines 10.8 kB
{"version":3,"file":"transform.mjs","names":[],"sources":["../src/transform.ts"],"sourcesContent":["import path from 'path';\nimport type {\n Stats,\n AssetStats,\n ChunkStats,\n ModuleStats,\n} from 'rollup-plugin-stats/extract';\n\nimport { getByteSize, getChunkId } from './utils';\n\n// https://github.com/relative-ci/bundle-stats/blob/master/packages/plugin-webpack-filter/src/index.ts\nexport type WebpackStatsFilteredAsset = {\n name: string;\n size?: number;\n};\n\nexport interface WebpackStatsFilteredChunk {\n id: number | string;\n entry: boolean;\n initial: boolean;\n files?: Array<string>;\n names?: Array<string>;\n}\n\nexport interface WebpackStatsFilteredModule {\n name: string;\n size?: number;\n chunks: Array<string | number>;\n}\n\nexport interface WebpackStatsFilteredConcatenatedModule {\n name: string;\n size?: number;\n}\n\nexport interface WebpackStatsFilteredRootModule extends WebpackStatsFilteredModule {\n modules?: Array<WebpackStatsFilteredConcatenatedModule>;\n}\n\nexport interface WebpackStatsFiltered {\n builtAt: number;\n hash?: string;\n assets: Array<WebpackStatsFilteredAsset>;\n chunks: Array<WebpackStatsFilteredChunk>;\n modules: Array<WebpackStatsFilteredRootModule>;\n}\n\nexport type ChunksIssuers = Record<string, Array<ChunkStats>>;\n\n/**\n * Recursivily check if a chunk is async based on the chunks issuers\n */\nexport const lookupChunkAsync = (\n chunksIssuers: ChunksIssuers,\n chunk: ChunkStats,\n processedChunks: Array<string> = [],\n cache: Map<string, boolean> = new Map()\n): boolean => {\n const cached = cache.get(chunk.fileName);\n if (cached !== undefined) {\n return cached;\n }\n\n const result = lookupChunkAsyncUncached(\n chunksIssuers,\n chunk,\n processedChunks,\n cache\n );\n\n cache.set(chunk.fileName, result);\n return result;\n};\n\nconst lookupChunkAsyncUncached = (\n chunksIssuers: ChunksIssuers,\n chunk: ChunkStats,\n processedChunks: Array<string>,\n cache: Map<string, boolean>\n): boolean => {\n // When the chunks are having a circular dependency, return true to continue the recursive check\n if (processedChunks.includes(chunk.fileName)) {\n return true;\n }\n\n if (chunk.isEntry) {\n return false;\n }\n\n if (chunk.isDynamicEntry) {\n return true;\n }\n\n const chunkIssuers = chunksIssuers[chunk.fileName];\n\n /**\n * A sync chunk without issuer chunks, is sync\n */\n if (!chunkIssuers) {\n return false;\n }\n\n const syncChunksIssuers = chunkIssuers.filter((chunkIssuer) => {\n return chunkIssuer.isDynamicEntry === false;\n });\n\n /**\n * A sync chunk with all the chunk issuer async, is async\n */\n if (syncChunksIssuers.length === 0) {\n return true;\n }\n\n /**\n * Recursively lookup for sync loads on the 2nd level issuers\n * - if at least one issuer is sync, the chunk is sync\n * - if none of the issuers are sync, the chunk is async\n */\n const nextProcessed = [...processedChunks, chunk.fileName];\n\n return syncChunksIssuers.every((issuer) =>\n lookupChunkAsync(chunksIssuers, issuer, nextProcessed, cache)\n );\n};\n\ntype AssetSource = ChunkStats | AssetStats;\ntype ChunkSource = ChunkStats;\ntype ModuleSource = { fileName: string } & ModuleStats;\n\n/**\n * Store transformed sources\n */\nclass TransformSources {\n constructor() {\n this.entries = {};\n }\n\n entries: Record<string, unknown> = {};\n\n push(id: string, source: AssetSource | ChunkSource | ModuleSource) {\n this.entries[id] = source;\n }\n\n /**\n * Get asset source\n */\n getByAsset = (asset: WebpackStatsFilteredAsset): AssetSource => {\n return this.entries[asset.name] as AssetSource;\n };\n\n /**\n * Get chunk source\n */\n getByChunk = (chunk: WebpackStatsFilteredChunk): ChunkSource => {\n return this.entries[chunk.id] as ChunkSource;\n };\n\n /**\n * Get module source\n */\n getByModule = (module: WebpackStatsFilteredModule): ModuleSource => {\n return this.entries[module.name] as ModuleSource;\n };\n}\n\nexport type TransformCallback = (\n stats: WebpackStatsFiltered,\n sources: TransformSources,\n bundle: Stats\n) => WebpackStatsFiltered;\n\nconst defaultTransform: TransformCallback = (stats) => stats;\n\nexport type BundleTransformOptions = {\n /**\n * Extract module original size or rendered size\n * default: false\n */\n moduleOriginalSize?: boolean;\n /**\n * Callback function to access and mutate the resulting stats after the transformation\n */\n transform?: TransformCallback;\n};\n\nexport const bundleToWebpackStats = (\n bundle: Stats,\n pluginOptions?: BundleTransformOptions\n): WebpackStatsFiltered => {\n const options = {\n moduleOriginalSize: false,\n ...pluginOptions,\n } satisfies BundleTransformOptions;\n const { moduleOriginalSize, transform = defaultTransform } = options;\n\n const assets: Array<WebpackStatsFilteredAsset> = [];\n const chunks: Array<WebpackStatsFilteredChunk> = [];\n const moduleByFileName: Record<string, WebpackStatsFilteredModule> = {};\n const sources = new TransformSources();\n const chunksIssuers: ChunksIssuers = {};\n\n const entries = Object.values(bundle);\n\n // Collect metadata\n entries.forEach((entry) => {\n if (entry.type === 'chunk') {\n entry.imports?.forEach((chunkImportFileName) => {\n // Skip circular references\n if (\n chunksIssuers[entry.fileName]?.find(\n (chunkIssuer) => chunkIssuer.fileName === chunkImportFileName\n )\n ) {\n return;\n }\n\n if (!chunksIssuers[chunkImportFileName]) {\n chunksIssuers[chunkImportFileName] = [];\n }\n\n chunksIssuers[chunkImportFileName].push(entry);\n });\n }\n });\n\n // Process data\n entries.forEach((entry) => {\n if (entry.type === 'chunk') {\n assets.push({\n name: entry.fileName,\n size: getByteSize(entry.code),\n });\n sources.push(entry.fileName, entry);\n\n const chunkId = getChunkId(entry);\n const chunkAsync = lookupChunkAsync(chunksIssuers, entry);\n\n chunks.push({\n id: chunkId,\n entry: entry.isEntry,\n initial: !chunkAsync,\n files: [entry.fileName],\n names: [entry.name],\n });\n sources.push(chunkId, entry);\n\n Object.entries(entry.modules).forEach(([modulePath, moduleInfo]) => {\n // Remove unexpected rollup null prefix\n const normalizedModulePath = modulePath.replace('\\u0000', '');\n\n const relativeModulePath = path.relative(\n process.cwd(),\n normalizedModulePath\n );\n\n // Match webpack output - add current directory prefix for child modules\n const relativeModulePathWithPrefix = relativeModulePath.match(/^\\.\\./)\n ? relativeModulePath\n : `.${path.sep}${relativeModulePath}`;\n\n const moduleEntry = moduleByFileName[relativeModulePathWithPrefix];\n\n if (moduleEntry) {\n moduleEntry.chunks.push(chunkId);\n } else {\n moduleByFileName[relativeModulePathWithPrefix] = {\n name: relativeModulePathWithPrefix,\n size: moduleOriginalSize\n ? moduleInfo.originalLength\n : moduleInfo.renderedLength,\n chunks: [chunkId],\n };\n sources.push(relativeModulePathWithPrefix, {\n fileName: modulePath,\n ...moduleInfo,\n });\n }\n });\n } else if (entry.type === 'asset') {\n assets.push({\n name: entry.fileName,\n size: getByteSize(entry.source),\n });\n sources.push(entry.fileName, entry);\n } else {\n // noop for unknown types\n }\n });\n\n const stats: WebpackStatsFiltered = {\n builtAt: Date.now(),\n assets,\n chunks,\n modules: Object.values(moduleByFileName),\n };\n\n let result: WebpackStatsFiltered;\n\n try {\n result = transform(stats, sources, bundle);\n } catch (error) {\n console.error(\n 'Custom transform failed! Returning stats without any transforms.',\n error\n );\n result = stats;\n }\n\n return result;\n};\n"],"mappings":"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"}