/* eslint-disable */ const { chalk, glob } = require('@umijs/utils'); const path = require('path'); const { Chunk, Compilation, Module, NormalModule } = require('../compiled/webpack'); const disabledFolders = ['node_modules', '.panda', '.panda-production', 'dist']; const detectDeadCode = (compilation, options) => { const assets = getWebpackAssets(compilation); const compiledFilesDictionary = convertFilesToDict(assets); const includedFiles = getPattern(options) .map(pattern => glob.sync(pattern)) .flat(); const unusedFiles = options.detectUnusedFiles ? includedFiles.filter(file => !compiledFilesDictionary[file]) : []; const unusedExportMap = options.detectUnusedExport ? getUnusedExportMap(convertFilesToDict(includedFiles), compilation) : {}; logUnusedFiles(unusedFiles); logUnusedExportMap(unusedExportMap); const hasUnusedThings = unusedFiles.length || Object.keys(unusedExportMap).length; if (hasUnusedThings && options.failOnHint) { process.exit(2); } }; const getPattern = options => options.patterns .map(pattern => path.resolve(options.context || '', pattern)) .concat(options.exclude.map(pattern => path.resolve(options.context || '', `!${pattern}`))) .map(convertToUnixPath); const getUnusedExportMap = (includedFileMap, compilation) => { const unusedExportMap = {}; compilation.chunks.forEach(chunk => { compilation.chunkGraph.getChunkModules(chunk).forEach(module => { outputUnusedExportMap(compilation, chunk, module, includedFileMap, unusedExportMap); }); }); return unusedExportMap; }; const outputUnusedExportMap = (compilation, chunk, module, includedFileMap, unusedExportMap) => { if (!(module instanceof NormalModule) || !module.resource) { return; } const path = convertToUnixPath(module.resource); if (!/^((?!(node_modules)).)*$/.test(path)) return; const providedExports = compilation.chunkGraph.moduleGraph.getProvidedExports(module); const usedExports = compilation.chunkGraph.moduleGraph.getUsedExports(module, chunk.runtime); if (usedExports !== true && providedExports !== true && includedFileMap[path]) { if (usedExports === false) { if (providedExports?.length) { unusedExportMap[path] = providedExports; } } else if (providedExports instanceof Array) { const unusedExports = providedExports.filter(item => usedExports && !usedExports.has(item)); if (unusedExports.length) { unusedExportMap[path] = unusedExports; } } } }; const logUnusedExportMap = unusedExportMap => { if (!Object.keys(unusedExportMap).length) { return; } let numberOfUnusedExport = 0; let logStr = ''; Object.keys(unusedExportMap).forEach((filePath, fileIndex) => { const unusedExports = unusedExportMap[filePath]; logStr += [ `\n${fileIndex + 1}. `, chalk.yellow(`${filePath}\n`), ' >>> ', chalk.yellow(`${unusedExports.join(', ')}`), ].join(''); numberOfUnusedExport += unusedExports.length; }); console.log( chalk.yellow.bold('\nWarning:'), chalk.yellow(`There are ${numberOfUnusedExport} unused exports in ${Object.keys(unusedExportMap).length} files:`), logStr, chalk.red.bold('\nPlease be careful if you want to remove them (¬º-°)¬.\n'), ); }; const getWebpackAssets = compilation => { const outputPath = compilation.getPath(compilation.compiler.outputPath); const assets = [ ...Array.from(compilation.fileDependencies), ...compilation.getAssets().map(asset => path.join(outputPath, asset.name)), ]; return assets; }; const convertFilesToDict = assets => assets .filter(file => Boolean(file) && disabledFolders.every(disabledPath => !file.includes(disabledPath))) .reduce((fileDictionary, file) => { const unixFile = convertToUnixPath(file); fileDictionary[unixFile] = true; return fileDictionary; }, {}); const logUnusedFiles = unusedFiles => { if (!unusedFiles?.length) { return; } console.log( chalk.yellow.bold('\nWarning:'), chalk.yellow(`There are ${unusedFiles.length} unused files:`), ...unusedFiles.map((file, index) => `\n${index + 1}. ${chalk.yellow(file)}`), chalk.red.bold('\nPlease be careful if you want to remove them (¬º-°)¬.\n'), ); }; const convertToUnixPath = path => path.replace(/\\+/g, '/'); module.exports = { detectDeadCode, disabledFolders }